自己做旅游攻略的网站,哪个网站建设服务器是在国外的,湘潭网站建设 多少费用磐石网络,layui做的网站摘要#xff1a;若干年前#xff0c;我写过一篇介绍浏览器渲染流水线的文章 - How Rendering Work (in WebKit and Blink)#xff0c;这篇文章#xff0c;一来部分内容已经过时#xff0c;二来缺少一个全局视角来对流水线整体进行分析#xff0c;所以打算重新写一篇新的文…摘要若干年前我写过一篇介绍浏览器渲染流水线的文章 - How Rendering Work (in WebKit and Blink)这篇文章一来部分内容已经过时二来缺少一个全局视角来对流水线整体进行分析所以打算重新写一篇新的文章从一个更高抽象层次和高度简化的方式对浏览器的渲染流水线进行解析能让大部分页端同学都能够看的明白并以此作为指引来分析和优化页面的渲染若干年前我写过一篇介绍浏览器渲染流水线的文章 - How Rendering Work (in WebKit and Blink)这篇文章一来部分内容已经过时二来缺少一个全局视角来对流水线整体进行分析所以打算重新写一篇新的文章从一个更高抽象层次和高度简化的方式对浏览器的渲染流水线进行解析能让大部分页端同学都能够看的明白并以此作为指引来分析和优化页面的渲染动画性能。有些基本概念如图层分块光栅化基本没有发生变化如果读者不理解的话请参考 How Rendering Work (in WebKit and Blink)本文不再过多解释。写这篇文章是始于年初对页端开发高性能交互/动画 Mobile WebApp 的一些思考实际开始写已经是年中... 陆陆续续写了几个月才终于写完。感觉最难的还是开始的部分要能够先把基础的部分讲解清楚然后再循序渐进去讲解更复杂的概念。思考了很久最终决定从帧的概念入手然后把动画定义成一个连续的帧序列的组合这种方式来对动画进行解析最终成文的效果还是比较让自己满意的。文章是在作业部落上写的的然后再把 Markdown 贴到其它网站无法保证每个网站最终呈现的排版效果如果读者觉得排版欠佳可以访问在作业部落上发布的原网址。本文基于当前版本的 Chrome 浏览器写成60 左右理论上部分知识可以应用于其它浏览器当然术语会有一定差别或者 Chrome 后续的版本但是并不完全保证这一点。1. 渲染流水线上图显示了 Chrome 一个高度简化后的渲染流水线示意图最底层的是 Chrome 最核心的部分 Blink负责JS的解析执行HTML/CSS解析DOM操作排版图层树的构建和更新等任务Layer Compositor图层合成器接收 Blink 的输入负责图层树的管理图层的滚动旋转等矩阵变幻图层的分块光栅化纹理上传等任务Display Compositor 接收 Layer Compositor 的输入负责输出最终的 OpenGL 绘制指令将网页内容通过 GL 贴图操作绘制到目标窗口上如果忽略掉操作系统本身的窗口合成器也可以简单认为是绘制在显示屏上当我们说 Compositor在没有加修饰语的情况下一般都是指 Layer Compositor。另外术语 Child Compositor子合成器也是指 Layer Compositor相对于作为 Parent 的 Display Compositor 而言。1.1 进程与线程一个 Chrome 浏览器一般会有一个 Browser 进程一个 GPU 进程和多个 Renderer 进程通常每个 Renderer 进程对应一个页面。在特殊架构Android WebView或者特定配置下Browser 进程可以兼作 GPU 进程或者 Renderer 进程意味着没有独立的 GPU 或者 Renderer 进程但是 Browser 跟 RendererBrowser 跟 GPURenderer 跟 GPU 之间的系统架构和通讯方式基本保持不变线程架构也是同样。Blink 主要运行在 Renderer 进程的 Renderer 线程我们通常会称之为内核主线程Layer Compositor 主要运行在 Renderer 进程的 Compositor 线程Display Compositor 主要运行在 Browser 进程的 UI 线程Display Compositor 后面应该会移到 GPU 进程的主 GPU 线程当然对父子合成器进行调度的部分仍然是在 Browser 进程的 UI 线程不太确定各个不不同平台的状况Android WebView 平台是已经实现了。1.2 帧所有的渲染流水线都会有帧的概念帧这个概念抽象描述了渲染流水线下级模块往上级模块输出的绘制内容相关数据的封装。我们可以看到 Blink 输出 Main Frame 给 Layer CompositorLayer Compositor 输出 Compositor Frame 给 Display CompositorDisplay Compositor 输出 GL Frame 给 Window。我们觉得一个动画是否流畅最终取决于 GL Frame 的帧率也就是目标窗口的绘制更新频率而觉得一个触屏操作是否响应即时取决于从 Blink 处理事件到 Window 更新的整个过程的耗时理论上应该还要加上事件从 Browser 发送给 Compositor再发送给 Blink 的这个过程的耗时。1.1.1 Main FrameMain Frame 包含了对网页内容的描述主要以绘图指令的形式或者可以简单理解为某个时间点对整个网页的一个矢量图快照可以局部更新。当前版本的 Chrome图层化的决策仍然由 Blink 来负责Blink 需要决定如何根据网页的 DOM 树来生成一颗图层树并以 DisplayList 的形式记录每个图层的内容未来图层化决策应该会转移到 Layer CompositorBlink 只输出 DisplayList 树和 DisplayList 节点的关键属性同时 DisplayList 不再以图层作为单位而是以每个排版对象作为单位。图层化决策一般由以下几个因素决定特殊元素如 PluginVideoCanvasWebGL维护正确的层级关系来保证绘制顺序是正确的比如 Overlap 的计算减少图层树的结构变更减少图层内容的变更目前 Blink 网页内容的变更是以图层为原子单位的如果以一个元素为根节点生成图层该元素的某些 CSS 属性如 Transform 的变更不会引起所属图层内容的变更第三点是可以被页端所直接控制来优化图层结构及 Main Frame 性能像传统的 translate3d hack 和新的 CSS 属性 will-change。1.2.2 Compositor FrameLayer Compositor 接收 Blink 生成的 Main Frame并转换成合成器内部的图层树结构因为图层化决策仍然由 Blink 负责所以这里的转换基本上可以认为是生成一棵同样的树再逐个对图层进行拷贝。Layer Compositor 需要为每个图层进行分块为每个分块分配 ResourceTexture 的封装然后安排光栅化任务。当 Layer Compositor 接收到来自 Browser 的绘制请求时它会为当前可见区域的每个图层的每个分块生成一个 Draw Quad 的绘制指令矩形绘制指令实际上指定了坐标大小变换矩阵等属性所有的 Draw Quad 指令和对应的 Resource 的集合就构成了 Compositor Frame。Compositor Frame 被发送往 Browser并最终到达 Display Compositor未来也可以直接发给 Display Compositor。1.2.3 GL FrameDisplay Compositor 将 Compositor Frame 的每个 Draw Quad 绘制指令转换一个 GL 多边形绘图指令使用对应 Resource 封装的 Texture 对目标窗口进行贴图这些 GL 绘图指令的集合就构成了一个 GL Frame最终由 GPU 执行这些 GL 指令完成网页在窗口上占据的可见区域的绘制。1.3 调度Chrome 渲染流水线的调度是基于请求和状态机响应调度的最上级中枢运行在 Browser UI 线程它按显示器的 VSync垂直同步周期向 Layer Compositor 发出输出下一帧的请求而 Layer Compositor 根据自身状态机的状态决定是否需要 Blink 输出下一帧。Display Compositor 则比较简单它持有一个 Compositor Frame 的队列不断的进行取出和绘制输出的频率唯二地取决于 Compositor Frame 的输入频率和自身绘制 GL Frame 的耗时。基本上可以认为 Layer Compositor 和 Display Compositor 是生产者和消费者的关系。2. 网页动画动画可以看做是一个连续的帧序列的组合。我们把网页的动画分成两大类 —— 一类是合成器动画一类是非合成器动画UC 内部也将其称为内核动画或者 Blink Animation虽然这不是 Chrome 官方的术语。合成器动画顾名思义动画的每一帧都是由 Layer Compositor 生成并输出的合成器自身驱动着整个动画的运行在动画的过程中不需要新的 Main Frame 输入非合成器动画每一帧都是由 Blink 生成都需要产生一个新的 Main Frame2.1 合成器动画合成器动画又可以分为两类合成器本身触发并运行的比如最常见的网页惯性滚动包括整个网页或者某个页内可滚动元素的滚动Blink 触发然后交由合成器运行比如说传统的 CSS Translation 或者新的 Animation API如果它们触发的动画经由 Blink 判断可以交由合成器运行Blink 触发的动画如果是 Transform 和 Opacity 属性的动画基本上都可以由合成器运行因为它们没有改变图层的内容。不过即使可以交由合成器运行它们也需要产生一个新的 Main Frame 提交给合成器来触发这个动画如果这个 Main Frame 包含了大量的图层变更也会导致触发的瞬间卡顿页端事先对图层结构进行优化可以避免这个问题。2.2 非合成器动画非合成器动画也可以分为两类使用 CSS Translation 或者 Animation API 创建的动画但是无法由合成器运行使用 Timer 或者 RAF 由 JS 驱动的动画比较典型的就是 Canvas/WebGL 游戏这种动画实际上是由页端自己定义的浏览器本身并没有对应的动画的概念也就是说浏览器本身是不知道这个动画什么时候开始是否正在运行什么时候结束这些完全是页端自己的内部逻辑合成器动画和非合成器动画在渲染流水线上有较大的差异后者更复杂流水线更长。上面四种动画的分类按渲染流水线的复杂程度和理论性能排列复杂程度由低到高理论性能由高到低合成器本身触发并运行的动画Blink 触发合成器运行的动画Blink 触发无法由合成器运行的动画由 Timer/RAF 驱动的 JS 动画长久以来浏览器渲染流水线的设计都主要是为了合成器动画的性能而优化甚至在某种程度上导致非合成器动画性能的下降比如说合成器的异步光栅化机制。不过这两年随着对 WebApp 渲染性能包括 WebGL 性能的重视并且随着主流移动设备的硬件性能持续提升合成器动画的性能也已经基本不成问题Chrome 的渲染流水线已经更多地针对非合成器动画的性能进行优化甚至会导致在某些特定状况下合成器动画性能的下降比方说倾向于为了维持图层树的稳定性减少变更而生成更多的图层。不过总的说来目前 Chrome 的渲染流水线在主流的移动设备上大部分场景下两者性能都能获得一个较好的平衡。3. 动画性能分析基础这里的性能分析主要是针对移动设备以桌面处理器的性能大部分场景下都不存在性能问题。目前移动设备的屏幕刷新率基本上都是 60hz而浏览器跟其它应用一样需要跟屏幕刷新保持垂直同步也就是动画帧率的上限是 60 帧这也是我们能够达到的最理想的结果。不过考虑浏览器本身的复杂程度可能有很多后台任务在运行而且操作系统本身也可能同时运行其它后台任务并且移动平台要考虑能耗和散热CPU/GPU 的调度策略会频繁地发生变化要完全锁定 60 帧是非常困难的。如果上限超过 60 帧实际平均帧率超过 60 反而不难但是如果上限是 60 帧垂直同步下要锁定 60 帧是非常困难的要求每一帧的各个环节耗时都要保持非常稳定。一般而言帧率在 55 60 之间已经可以认为是非常优秀的水平这时用户几乎感觉不到卡顿帧率在 50 55 之间可以认为是良好的水平用户感觉到轻微卡顿但整体来说还是比较流畅要达到 50 帧以上的水平我们就需要对动画在渲染流水线的每个重要环节进行性能计算需要知道这些环节最长允许的耗时上限和网页影响这些环节耗时的主要原因虽然实际上很难完全锁定 60 帧但是一般来说性能分析/优化还是会以 60 帧为目标来倒推各个环节的最大耗时。如果是场景比较复杂的 Canvas/WebGL 游戏以 30 帧为目标帧率是一个合理的诉求。3.1 光栅化机制在对动画性能进行分析之前需要先说明一下目前的 Chrome 的光栅化机制。合成器会监控是否需要安排新的光栅化任务当需要光栅化调度时合成器找到所有在当前可见区域的图层合成器找到这些图层在当前可见区域的分块合成器检查这些分块是否需要光栅化如果需要生成一个对应的光栅化任务并分配所需要的 Resource 放入任务队列里面Renderer 进程会预先创建一个或者多个 Worker 线程移动平台一般是两个这些线程会从任务队列里面顺序取出每一个光栅化任务并运行光栅化任务运行后会通知合成器合成器根据需要检查哪些任务已经完成已经完成的任务 Resource 会转交给对应的分块实际的光栅化区域会比当前可见区域要更大一些一般是增加一个分块大小单位对不可见区域的预光栅化有助于提升合成器动画的性能和减少出现空白的几率。从上可知合成器的光栅化调度完全是异步的合成器在 Compositor 线程需要执行的就是安排光栅化任务和检查哪些任务已经完成Compositor 线程本身不会被真正运行光栅化任务的 Worker 线程所阻塞。4 合成器动画性能分析和优化指南4.1 动画流水线上图显示了合成器动画的渲染流水线示意图根据 Android WebView 平台的实现进行绘制其它平台可能略微不同但对后面的性能分析在大部分情况下影响不大整个流水线的大概过程是位于 Browser 进程 UI 线程的窗口管理器接收到来自操作系统的屏幕刷新垂直同步信号VSync开始准备输出新的一帧它首先给位于 Renderer 进程 Compositor 线程的 Layer Compositor 发送一个 Begin Frame 消息Layer Compositor 接收到 Begin Frame 消息后更新合成器内部的状态机开始准备输出 Compositor Frame在这个过程中的一个重要动作就是 Animate合成器会检查当前是否有正在运行的动画然后运行这些动画并根据动画运行的结果改变关联图层的对应属性比如惯性滚动动画改变图层的 Scroll OffsetTransform 动画改变图层的 TransformAnimate 的结果会发送回给 UI 线程告诉其是否有动画正在运行需要更新窗口如果 UI 线程确定合成器需要更新窗口则会发送一个 Draw 消息请求合成器输出下一帧 Compositor Frame合成器按下面的过程产生新的 Compositor Frame 并发送给 Display Compositor 4.1 合成器找出在当前可见区域内显示的图层 4.2 合成器找出这些图层在可见区域内的分块 4.3 如果该分块已经有分配 Resource说明此分块已经完成光栅化则产生一个 Draw Quad 的命令置入 Compositor Frame 中如果没有则跳过Display Compositor 接受到新的 Compositor Frame 后对 Compositor Frame 进行 Render将每一个 Draw Quad 命令转换成一个 GL Draw Call然后 GPU 执行所有的 GL 指令完成最后的窗口绘制上述流程的一些关键点是Draw 的过程中合成器不会等待可见的分块光栅化完成这让合成器充分利用了异步光栅化的机制来提升性能但是也会造成动画过程中可能会出现空白的分块比如快速滚动页面有时会看到空白区域在合成器动画过程中Layer Compositor 和 Display Compositor 是异步并发的在 Display Compositor 输出 GL Frame N 的时候Layer Compositor 已经可以开始输出下一帧 Compositor Frame N 1这意味只要让 Compositor Frame N 和 GL Frame N 的耗时各自都在 16.7 毫秒以内就可以实现 60 帧的动画4.2 动画耗时分析Begin Frame 的耗时一般很短大概 1 2 毫秒左右Draw 的耗时也不长一般在 3 5 毫秒左右耗时主要取决于网页的图层复杂度总的来说合成器动画过程中 Compositor 线程的开销一般都不会构成性能瓶颈Render 的耗时也不长同样在 3 5 毫秒左右耗时主要取决于当前可见区域内的可见分块的数量GPU 部分的耗时比较长耗时主要取决于当前可见区域内的可见分块的总面积也就是绘制的总面积一旦 Render GPU 部分的耗时大于 16.7 毫秒动画就会出现掉帧总的来说影响合成器动画性能的最关键因素就是过度绘制系数Overdraw可以理解为绘制的面积和可见区域面积的比例如果网页本身存在大量图层堆叠情况导致过度绘制系数过高就会严重影响合成器动画的性能。经验显示过度绘制系数比较理想的值是在 2 以内一般建议不超过 3这样可以保证在中低端的移动设备上也有不错的性能表现。另外合成器动画过程中Compositor 和 GPU 线程是前台线程它们虽然理论上不会被 Worker 和 Renderer 线程阻塞但是在真实的运行场景中移动设备的 CPU/GPU 和内存带宽等硬件资源是有限的如果 Worker 和 Renderer 线程处于高负荷状态下也会导致前台的 Compositor 和 GPU 线程阻塞最终导致合成器动画掉帧。这种现象常见于网页在合成器动画比如惯性滚动过程中有大量的 JS 加载图片或者其它内容并频繁地对 DOM 树进行操作网页的图层树非常复杂并且其结构在合成器动画过程中频繁发生变化导致大量的光栅化任务在 Worker 线程运行4.3 动画性能优化 Checklist根据上述的耗时分析我们可以给出一个页端优化合成器动画性能的简单 Checklist检查网页的图层结构是否合理包括深度和数量一般来说深度在 10 以内数量在 100 以内是比较合理的值检查网页的合成器动画包括网页的惯性滚动各种图层的淡入/淡出等动画在动画过程中是否同时存在大量的网络加载和 DOM 操作网页图层结构是否保持稳定当网页处于任一滚动位置上时它的当前过度绘制系数是否合理如何判断网页的图层结构是否稳定一般而言如果是位于叶子节点的图层增加或者移除对整个图层结构影响并不大但是如果是中间节点的图层增加或者移除对图层结构的影响就比较大了并且越是接近根节点影响就越大。现在的页端都会大量使用异步加载来优化加载性能和流量但是容易出现导致动画掉帧的现象。要平衡好这一点意味着需要实现一个加载和关联 DOM 操作的调度器如果检查到动画正在运行则停止加载或者通过节流阀机制降低加载的并发数量和频率同时可以通过事先生成相应的 DOM 节点和图层作为占位符来避免加载后的图层结构发生剧烈变化。5 非合成器动画性能分析和优化指南前面已经我们已经把非合成器动画区分为 Blink 触发无法由合成器运行的动画和由 Timer/RAF 驱动的 JS 动画两类因为前者可以认为是后者的一个简化版本所以这一章主要讨论 Timer/RAF 驱动的 JS 动画。5.1 动画流水线从上图可以看出非合成器动画的流水线比合成器动画更长更复杂并且非合成器动画的后半段跟合成器动画是一致的。JavaScipt 部分是页端实现的逻辑可能包含了计算的部分和调用浏览器提供的 API 的部分修改 DOM 树CSS 属性等最终改变了网页的内容网页内容被改变会导致 Blink 生成新的 MainFrameMainFrame 包括了重排版更新图层树和重新记录发生变更的图层的内容生成新的 DisplayList等等Blink 生成新的 MainFrame 后需要向合成器发起 Commit 的请求合成器在 Commit 过程中根据 MainFrame 生成自身的图层树Blink 在 Commit 的过程中保持阻塞状态Commit 完成后再继续运行合成器实际上有两棵图层树新提交的 MainFrame 生成的是 Pending 树用于绘制 Draw 的是 Active 树只有当 Pending 树当前可见区域部分的分块全部完成 Rasterize 后才会进入 Active 步骤在 Active 的过程中Pending 树相对于 Active 树的变更部分才会被同步到 Active 树Active 后合成器会向 UI 线程的窗口管理器发起重绘请求窗口管理器会在下一个 VSync 的时候开始绘制新的一帧后面的流程就跟合成器动画是一样的了上述流程的一些关键点是在合成器动画中分块没有完成光栅化出现空白是被允许的这样浏览器可以更好地保证合成器动画的帧率但是在非合成器动画中出现空白是不被允许的因为新的 MainFrame 常常会带来大面积的变更如果允许空白的话可能会出现非常不好的视觉效果。这样就导致合成器需要使用两棵图层树来构建一个类似双缓冲的机制只有当 Pending 树在后台完成可见区域的光栅化时才被允许同步到 Active 树在非合成器动画过程中Main Frame NMain Frame N ActiveCompositor Frame NGL Frame N 这四个 Block 基本上可以认为是可以并发运行的唯一会阻塞的环节是 Commit不过 Commit 耗时一般不长理论上我们要实现 60 帧的非合成器动画只需要保证其中每个 Block 的耗时总和小于 16.7 毫秒即可。当然实际的状况下在移动设备上很难实现这么多线程完全并发运行加上过多线程带来的互相通讯的开销使得每个 Block 的最大允许耗时实际上是小于 16.7 毫秒的5.2 动画耗时分析和优化指南JavaScipt 的耗时是由页端自己的逻辑决定的一般超过 10 毫秒就基本上很难实现 60 帧的非合成器动画了MainFrame 的耗时主要取决于网页 DOM 树图层树的复杂程度和变化程度在变更很小比如只有几个元素的内容发生变化图层树不变的情况下一般耗时都是在 3 5 毫秒左右如果变更很大几十甚至几百都是有可能的Commit 的耗时主要取决于图层树的复杂程度一般耗时都很短大概 2 ~ 3 毫秒上下Rasterize 的耗时范围变化极大取决于网页内容的复杂程度和新 MainFrame 在当前可见区域内网页内容发生变化的总面积另外图片解码也发生在这个阶段而图片解码也是光栅化耗时最多的一个环节光栅化的耗时从几毫秒到几百毫秒都有可能图片在第一次被光栅化时被解码一直在可见区域内的图片不会被反复重解码Active 跟 Commit 的耗时类似主要取决于图层树的复杂程度一般耗时很短大概 2 ~ 3 毫秒上下总的来说对非合成器动画性能影响最大的通常是 JavaScript 和 Rasterize要实现高性能的非合成器动画页端需要很小心地控制 JavaScript 部分的耗时并避免在每一帧中引入大面积的网页内容变化和大幅度的图层结构变化。另外非合成器动画的后半段就是合成器动画所以对合成器动画的性能优化要求也同样适用于非合成器动画。另外对于 WebGL 来说当在 JavaScript 里面调用 WebGL API 时这些命令只是被 Chrome 缓存起来并不会在 Renderer 线程调用真正的 GL API所以 WebGL API 在 JavaScript 部分的耗时只是一个 JS Binding 调用的 Overhead最终绘制 WebGL 内容的 GPU 耗时实际上是被包含在最后的 GPU 的步骤里面。但是在移动平台上一个 JS Binding 调用的 Overhead 是相当高的大概在 0.01 毫秒这个范围所以每一帧超过 1000 个 WebGL API 调用的 WebGL 游戏性能阻塞的瓶颈有很大概率会出现在 JavaScript 也就是 CPU 上而不是 GPU。6 浏览器渲染流水线未来的演化看完非合成器动画流水线分析的读者第一感觉恐怕是觉得太过复杂了比起合成器动画更多的线程更多的中间环节有时即使每个环节都做到了完美最终也有可能因为线程之间的通讯和等待而导致掉帧。正如前面所说的一样长久以来浏览器的渲染流水线都是为了合成器动画而优化的它的主要特征是利用图层缓存作为中间媒介将光栅化和合成分离让光栅化完全独立异步光栅化的机制避免合成器的阻塞这两点实际上是有利于合成器动画而不利于非合成器动画的浏览器渲染流水线当前和未来的演化理所应当地要解决这些问题。Firefox 新的渲染引擎 WebRender 比较激进看起来是采用流行的 UI Toolkit 如 QtAndroid 的 DisplayList/Scene Graph Direct Rasterize 的方式这样当然是有利于非合成器动画的但是否会造成合成器动画的性能衰退还很难说。Direct Rasterize 的前提条件是浏览器渲染引擎要实现 GPU 光栅化并且性能和兼容性要足够好。Chrome 当前也是在努力推进 GPU 光栅化即使整个流水线的架构没有发生变化GPU 光栅化也可以大幅度减少光栅化在 Worker 线程部分的 CPU 耗时将这些耗时转移到 GPU 线程上去这样 Main Frame N Active 这部分的耗时就可以大大减少了。从目前的统计数据来看可以使用 GPU 光栅化的移动设备占比已经很高大概 6 成以上的样子随着老旧设备的淘汰这个比例未来会越来越高并且随着 GPU 性能的提升GPU 光栅化的效果也会越来越好。Chrome 另外一个重点改进渲染性能的项目是 Slim Painting未来 Blink 跟 Firefox 类似只输出 DisplayList 树并且每棵 DisplayList 不再是以图层为单位而是以排版对象为单位这样合成器可以更自由地选择 Layerize 和 Rasterize 的策略Async 或者 On Demand 或者 Direct Rasterize 都可以混用来最大化动画的性能合成器可以变得更 Adaptive 而不像现在光栅化和合成区隔的那么泾渭分明。除了浏览器持续改进自身渲染流水线外提供更多 API 供页端使用来最大化 WebApp 的性能也是一个重要的方向包括合成器实现更多的动画效果然后通过 API 供页端直接使用这样页端可以避免自己使用 JavaScript 实现比如 CSS Snap Point;更多的有助于帮助页端将耗时的 JavaScript 部分从 Renderer 线程迁移到其它 Worker 线程让 WebApp 可以更充分地利用硬件的多核能力通过并发来提升性能比如 Offscreen Canvas但是这些新特性要真正应用起来对页端对浏览器渲染流水线的理解要求就更高了。作者小扎zack原文地址click.aliyun.com/m/43542/