不管是从出于性能、开发工作量考虑,还是追求整体App内体验一致性,总会有有替换wap页面的元素替换成Native元素的需求。这其中,改动大收益高的方案就是 React Native和Weex了,以React或者Vue的形式来写页面,然后用虚拟Dom映射成不同平台下的原生控件。想法和实现都很棒,不过真实开发体验中,还是有不少的问题,相对来说整体方案也较为重,需要改写整个页面,也需要踩一些大大小小的坑。
这里我将尝试一种更轻量的方式来实现"替换",仅供参考。更准确的说,不是"替换"元素,而是覆盖元素,即在Webview页面占位区域上贴Native控件。
红色部分为Native控件,选中蓝色区域为整块的Webview
方案实现上比较简单直接:
- 在webview上加载所需渲染的url
- 将webview将所需的显示区域通知Native控件,Native将该控件加在webview的视图上
- Native监听webview的滚动,根据y轴上的偏移量来移动native元素,以看起来像是webview的"一部分"
以下以iOS实现为例:
这里的第一步就是正常的加载url;第二步中的所需的显示区域需要前端来传递,可以根据document.getElementById()
来获取。该方法可以获取这个占位元素的坐标信息,将offsetLeft
、offsetTop
、offsetHeight
、offsetWidth
传给native即可,Native根据这些信息设置初始frame
,并存储初始y
以为后面调整滚动做准备。然后在webview
上addSubview
添加到目标视图上。这里对于前端的兼容来说,只要判断是否在App内,如果是则用占位元素为Native控件留出空间,如果不是则使用自身的组件。 第三步,可通过监听webview
内的scrollView
来实现,用RAC
可写为:
@weakify(self)
[RACObserve(self.webView.scrollView,contentOffset.y) subscribeNext:^(id x) {
@strongify(self)
self.videoView.y = self.baseY - self.webView.scrollView.contentOffset.y;
}];
监听y
方向上的偏移量,配合初始y
坐标计算即可在视觉上实现跟随webview
上同步滚动的效果。
最终效果如下:
这个方案的缺陷:
- 页面层级是固定的,即添加的native的控件只是视觉上在
webview
上而已,它实际在整个webview
的上层。 - 因为页面层级的问题,手势并不会传递给
webview
,在这个层面上是割裂的。 - 假如页面是动态的,那么每次native组件上方的控件进行了变动,需要及时通知,以便同时调整。
第一个问题在大多数场景上尚能接受,元素覆盖的情况较少。假如真要做,可以考虑覆盖的同时计算实际需要展示的页面,再截native组件元素;或者将整个webview拆分为不同的小webview,这样层级就好控制些了。
第二个问题,假如是纯展示的组件,可以直接设置为userInteractionEnabled
为NO
,手势就会传递给webview
,否则就要由native再将手势传递给webview,或者在组件里进行特定方向上的手势过滤。
这个方案好在轻量,比较容易集成。
本方案为试验性的尝试,在获取控件的区域中使用了document.getElementById()
会有一定的性能损耗,因此通过这个方案的替换,在性能上的优化并没有量化的测试,仅供参考。