riley!我刚刚测试上架直播间产品,同事都说看不见!
riley!测试小哥哥反馈购物车消失了,急求解决办法
怎么会这样?!究竟是哪里出了问题?
随着小程序生态的蓬勃发展,小程序逐渐成为获客营销的新渠道,直播业务也能够通过小程序触达用户。然而,直播小程序开发并不是一件容易的事情,例如直播小程序变成 “纯净模式”,即只看到直播页面,却无法看到弹幕、产品链接、购物车等信息。这个难题应该如何解决呢?
经过技术检查,直播小程序 “纯净模式” 问题出现在对原生组件的层级应用,通过同层渲染的方法即可实现高效处理。
在内置组件中,有一类组件较为特殊,它们并不完全在 Exparser 的渲染体系下,而是由客户端原生参与组件的渲染,这类组件称为 「原生组件」,例如小程序中的 live-player、canvas、map 等都是原生组件。
- 扩展 Web 的能力:例如输入框组件 input、textarea 有更好的控制键盘能力
- 减轻 WebView 的渲染工作:例如地图组件 map 这类较复杂的组件,其渲染工作不占用 WebView 线程,而交给更高效的客户端原生处理
- 绕过 setData、数据通信和重渲染流程,渲染性能更好:例如画布组件 canvas 可直接用一套丰富的绘图接口进行绘制
然而原生组件也存在一定的限制。由于原生组件脱离在 Webview 渲染流程外,因此其层级是最高的。页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上面。后插入的原生组件可以覆盖之前的原生组件。例如下左图所示,非原生组件位于 WebView 层,而原生组件及 cover-view、cover-image 位于另一个较高的层级。所以 live-player 组件把其他业务组件都覆盖了,导致部分用户只能看到 “纯净模式”。
明晰原理后,解决这个问题的方法就是需要把原生组件直接渲染到 WebView 层级上面,即通过微信团队制定的「同层渲染」解决方案实现层级合理安排。例如正如上右图所示,通过同层渲染,原生组件层已经不存在,原生组件被直接挂载到 WebView 节点上。
为了解决原生组件的层级问题,同时尽可能保留原生组件的优势,微信团队提出 同层渲染,即控件并非绘制在原生组件贴片层,而是绘制在 WebView 所渲染的页面中,与其他 HTML 控件在同一层级。支持同层渲染后,原生组件与其它 H5 组件可以随意叠加,层级的限制将不复存在。
了解同层渲染的优势后,如何合理应用同层渲染,实现层级合理应用?基于同层渲染在 iOS 和 Android 平台下的实现不同,以下分别介绍两个平台的实现方案。
小程序 iOS 端的同层渲染基于 WKChildScrollView 实现,原生组件在 attached 之后会直接挂载到预先创建好的 WKChildScrollView 容器下,大致的流程如下:
- 小程序前端在 webview 内创建一个 DOM 节点并设置其 CSS 属性为 overflow: hidden 且 -webkit-overflow-scrolling: touch,生成一个containerId,并将这个 WKChildScrollView 的位置信息通知给客户端
- 前端通知客户端递归搜索查找到该 DOM 节点对应的原生 WKChildScrollView 组件
- 将原生组件挂载到该 WKChildScrollView 节点上作为其子 View
- WebKit 内核已经处理了 WKChildScrollView 与对应 DOM 节点之间的层级关系
通过上述流程,小程序的原生组件就被插入到 WKChildScrollView,即是在步骤 1 创建的 DOM 节点映射的原生 WKChildScrollView 节点。此时,修改这个 DOM 节点的样式属性同样也会应用到原生组件上。因此,同层渲染的原生组件与普通的 H5 组件表现并无二致。
Android 端需要经历「解析-排版-绘制-合成」的内核渲染过程,从构建 DOM Tree到最终将 Layer 按照一定的顺序合成在一起,交给系统的 Frame Buffer,实现输出到屏幕上。
从内核渲染流程可以看出,实现原生组件同层渲染,需要将原生组件作为一个Layer 插入到 Layer Tree。如果能够将原生组件渲染到内核提供的 Texture 上,就可达到同层渲染的目的。Android 端的同层渲染就是基于 <embed /> 标签结合 chromium 内核扩展来实现的, 大致流程如下:
- WebView 侧创建一个 embed DOM 节点并指定组件类型
- chromium 内核会创建一个 WebPlugin 实例,并生成一个 RenderLayer
- Android 客户端将原生组件的画面绘制到步骤 2 创建的 RenderLayer 所绑定的 SurfaceTexture 上
- 绘制完成后内核收到 SurfaceTexture 内容更新的通知,通知 chromium 内核渲染该 RenderLayer
- 当同层渲染的节点收到事件时,会将事件转发给 Native 组件模块处理。如果 Native 组件不消费事件,内核会再将事件向上冒泡
通过以上流程,实现把一个原生组件渲染到 WebView 上,相当于给 WebView 添加一个外置的插件。
如果用户的设备没有微信自研的 chromium 内核,则会无法切换至同层渲染,此时会在组件初始化阶段触发 bindrendererror 事件。因此在必要时,根据该回调做好 UI 的 fallback。
iOS 端常见问题
如果在基础库创建同层节点时,节点发生了 WXSS 变化从而引起 WebKit 内核重排,此时可能会出现同层失败的现象。因此应尽量避免在原生组件上频繁修改节点的 WXSS 属性,尤其要尽量避免修改节点的 position 属性。如需对原生组件进行变换,建议使用 transform 而非修改节点的 position 属性。
由于原生组件层级比所有在 Webview 层渲染的普通组件都要高,因此通过同层渲染实现将原生组件直接渲染到 Webview 层级上,最终实现小程序层级的正确显示。关于原生组件的更多内容,请点击 官方文档 了解更多。