微信小程序『同层渲染』技术是怎么回事?

前言

Webview 资源离线化之后不需要从后端拉取资源,省去了资源加载耗时减少等待时间,用户体验得到了明显提升,但受限于 Webview 的天生限制在某些场景下仍然能感受到与原生明显的体验落差。比如输入框弹出键盘时的页面卡顿、图片较多的页面内存占用过从后台回来前台时页面被重新加载、WebP 图片支持碎片化、图片不能跨页共享内存、视频播放组件体验不佳等问题。在 Webview 本身受限的情况下,如何在体验上向原生靠近是我们不得不面对的问题。如果能将影响用户体验的关键可交互组件用原生组件进行替换可以从根本上解决体验不如原生的问题。这部分内容我称为: WebView原生化

Webview 原生化

Webview 原生化是指把 Webview 内部分占位 DOM 元素用原生组件进行替换,原生组件与原始 Webview 混合便得到了用户看到的最终界面。组件替换有两种方式,一种是在 Webview 的直接上层添加原生视图蒙层,把原生组件添加到蒙层上占位 DOM 对应的位置。另一种是将原生组件添加到 Webiview 渲染时与占位 DOM 对应的合成层(独立出来的原生视图)上,下面会对比两种方案的差异。

传统方案

方案简介

将原生组件添加到 Webview 上的蒙层是传统方案。思路也很简单,将 Webview 内要用原生替换的组件用占位 DOM 进行封装,占位 DOM 被展示时发送 DOM 的类型、位置、形状等其它信息给到原生。原生在拿到信息后生成对应的原生组件添加到蒙层之上。

传统方案缺陷

原生组件的渲染实际上是完全独离于 Webview 的渲染流程,甚至是在整个 Webview 的视图之外。这就也意味着:

  • 原生组件的层级是最高的:页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上;

  • 部分 CSS 样式无法应用于原生组件;

  • 原生组件无法在 scroll-view、swiper、picker-view、movable-view 中使用:因为如果开发者在可滚动的DOM区域,插入原生组件作为其子节点,由于原生组件是直接插入到webview外部的层级,与DOM之间没有关联,所以不会跟随移动也不会被裁减

这也就是说当一个原生组件被添另到 Webview 上时它永远处顶层,不会被 Webview 内的弹窗覆盖,在滚动时原生组件会盖住原本应被显示的区域。

同层渲染

方案简介

原生组件与 DOM 图层在同一层级渲染的方式叫同层渲染。

WKWebview 在进行渲染操作时,会将若干个 DOM 元素混合(Composition)后渲染到原生合成视图(WKComponiitingView)上,如果能将原生合成视图与指定 DOM 进行对应那便可以将原生组件直接添加到合成视图上。

如上图中选中的图层就是由多个 DOM 合成显示在一起,只要将其中的图片部分独立出一个单独的图层渲染,把原生图片添加到独立出来的原生视图就可以实现同层渲染。其前置条件是:

当把 DOM 节点的 CSS 属性设置为 overflow: scroll (低版本需同时设置 -webkit-overflow-scrolling: touch)之后,原生 WKWebView 会为其生成一个对应的 WKChildScrollView

具体实现流程为:

  1. 小程序前端,在webview内创建一个 DOM 节点并设置其 CSS 属性为 overflow: hidden 且 -webkit-overflow-scrolling: touch;且保证其有个比当前 DOM 大的子 DOM,可添加1px的外边距解决;

  2. 前端通知客户端查找到该 DOM 节点对应的原生 WKChildScrollView 组件;

  3. 将原生组件挂载到该 WKChildScrollView 节点上作为其子 View;

  4. WebKit 内核已经处理了WKChildScrollView与对应DOM 节点之间的层级关系;

前端建创原生视图节点的 DOM 样式示例:

html 复制代码
<div class="container cid_1" data-component-type="input" style="width: 200px; height: 40px"> 
    <div style="width: 101%; height: 101%">&nbsp;</div>
</div>

<style>     
	.container {  /* insert WKChildView in WKWebView */
		overflow: scroll; -webkit-overflow-scrolling: touch;    
 	}
 </style>

把 DOM 节点按前置条件设置之后,WebKit 引擎将图片部分独立出来并插入了一个 WKChildScrollView,只需要在视图树上找到对应的 WKChildScrollView 把原生的 UIImageView 添加到 WKChildScrollView 即可。添加后的原生视图可跟随页面一起滚动并保留了 DOM 树深度信息,不会覆盖原本应该在上层的组件。

如何映射 DOM 到原生视图

独立出来的 WKCompositingView 及其子视图 WKChidScrollView 如何与 DOM 对应呢?通过打印 WKCompositingView 的 description 属性发现其中包含了类型(div)及 class 信息, 可以通过给每个需要原生化的组件一个唯一 class 名称,前端通过收集 DOM 属性,把 class 信息传递给原生,原生遍历查找视图树即可。

如何在原生视图树中查找 「DOM」

WKWebview 渲染出的视图树可能非常复杂层级较多,如果每次都去遍历视图树在快递滚动的场景可能会出现性能问题。由于与 DOM 对应的 WKCompositingView 是唯一的,可以通过一个弱引用 Map 去记录每个 class 对应的 WKCompositingView,避免每次都去遍历整个树。

前端原生组件封装

DOM 节点与原生视图的对应关系搞定之后,需要前端封装对应「虚拟组件」。虚拟组件的作用是用来标示位置,大小,形状等信息不用于实际的渲染,渲染通过调用原生的方法(通过 Bridge)由原生处理。此方案正是微信小程序目前使用的方案,目前微信的 canvas、map、animation-view、textarea、cover-view、cover-image、camera、video、live-player、input 等组件都是由原生进行渲染。微信只提供了实现思路,当中的细节处理还需要进一步探索。

相关推荐
2401_8658548810 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom1 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈1 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403551 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔1 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter1 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone
关键帧Keyframe2 天前
音视频面试题集锦第 7 期
音视频开发·视频编码·客户端
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
pb82 天前
引入最新fluwx2.5.4的时候报错
flutter·ios
袁代码2 天前
Swift 开发教程系列 - 第4章:函数与闭包
ios·swift·ios开发