- 原文地址:The Anatomy of a Frame
- 原文做者:Paul
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:WangLeto
- 校对者:Xuyuey, Fengziyin1234, L9m
开发者经常问我关于像素工做流程的某些部分,何时、为何、发生了什么。因此我感受值得提供一些参考,有关于将像素显示在屏幕上的过程里发生了什么。前端
警告:文本是 Blink(译注:Chrome 使用的排版引擎,是 webkit 的分支)和 Chrome 的视角。主线程的大部分任务以某种方式被全部第三方(vendors)任务“共享”,好比布局和样式计算结果,可是总的架构可能不是这样。android
这是真的,让咱们先看一张图:ios
将像素放到屏幕上的完整过程。git
下载图片github
这张小图上放了太多内容,因此让咱们详细看些定义。将上图与这些定义结合起来可能会有帮助。web
让咱们从进程开始看:canvas
如今看一下渲染进程中的线程。后端
在许多方面,你都应该把合成线程看作“老大”。虽然这个线程不运行 JavaScript,不进行布局、绘制内容或者其余任务,可是它全权负责启动主线程工做,并将帧运送到屏幕上。若是合成线程不用等待输入事件的处理,就能够在等待主线程完成工做时把帧发送出去。浏览器
你也能够想象 Service Worker 和 Web Worker 存在于渲染进程中,虽然我把他们排除在外了,由于他们把事情弄得很复杂。架构
主线程风貌全览。下载图片
让咱们从垂直同步信号到像素,逐步分析这个过程,而后讨论一下在彻底版本中事件是怎么工做的。记住这一点:浏览器并不须要执行全部步骤,具体状况取决于哪些步骤是必需的。例如,若是没有新的 HTML 要解析,那么解析 HTML 的步骤就不会触发。事实上,一般提高性能的最佳方法,只是简单地移除流程中部分步骤被触发的须要!
一样值得注意的是,上图中 RecalcStyles 和 Layout 下方指向 requestAnimationFrame
的红色箭头。在代码中刚好触发这两个状况是彻底可能的。这种状况叫作强制同步布局(或强制同步样式,Forced Synchronous Layout 和 Forced Synchronous Styles),一般于性能不利。
开始新的一帧。垂直同步信号触发,开始渲染新的一帧图像。
输入事件的处理。从合成线程将输入的数据,传递到主线程的事件处理函数。全部的事件处理函数(touchmove
,scroll
,click
)都应该最早触发,每帧触发一次,但也不必定这样;调度程序会尽力尝试,可是是否真的每帧触发因操做系统而异。从用户交互事件,到事件被交付主线程,两者之间也存在延迟。
requestAnimationFrame
。这是更新屏幕显示内容的理想位置,由于如今有全新的输入数据,又很是接近即将到来的垂直同步信号。其余的可视化任务,好比样式计算,由于是在本次任务以后,因此如今是变动元素的理想位置。若是你改变了 —— 好比说 100 个类的样式,这不会引发 100 次样式计算;它们会在稍后被批量处理。惟一须要注意的是,不要查询进行计算才能获得的样式或者布局属性(好比 el.style.backgroundImage
或 el.style.offsetWidth
)。若是你这样作了,会致使从新计算样式,或者布局,或者两者都发生,进一步致使强制同步布局,乃至布局颠簸。
解析 HTML(Parse HTML)。处理新添加的 HTML,建立 DOM 元素。在页面加载过程当中,或者进行 appendChild
操做后,你可能看到更多的此过程发生。
从新计算样式(Recalc Styles)。为新添加或变动的内容计算样式。可能要计算整个 DOM 树,也可能缩小范围,取决于具体更改了什么。例如,更改 body 的类名影响可能很大,可是值得注意的是浏览器已经足够智能了,能够自动限制从新计算样式的范围。
布局(Layout)。计算每一个可见元素的几何信息(每一个元素的位置和大小)。通常做用于整个文档,计算成本一般和 DOM 元素的大小成比例。
更新图层树(Update Layer Tree)。这一步建立层叠上下文,为元素的深度进行排序。
Paint。过程分为两步:第一步,对全部新加入的元素,或进行改变显示状态的元素,记录 draw 调用(这里填充矩形,那里写点字);第二步是栅格化(Rasterization,见后文),在这一步实际执行了 draw 的调用,并进行纹理填充。Paint 过程记录 draw 调用,通常比栅格化要快,可是两部分一般被统称为“painting”。
合成(Composite):图层和图块信息计算完成后,被传回合成线程进行处理。这将包括 will-change
、重叠元素和硬件加速的 canvas 等。
栅格化规划(Raster Scheduled)和栅格化(Rasterize):在 Paint 任务中记录的 draw 调用如今执行。过程是在合成图块栅格化线程(Compositor Tile Workers)中进行,线程的数量取决于平台和设备性能。例如,在 Android 设备上,一般有一个线程,而在桌面设备上有时有 4 个。栅格化根据图层来完成,每层都被分红块。
帧结束:各个层的全部的块都被栅格化成位图后,新的块和输入数据(可能在事件处理程序中被更改过)被提交给 GPU 线程。
发送帧:最后,但一样很重要的是,图块被 GPU 线程上传到 GPU。GPU 使用四边形和矩阵(全部经常使用的 GL 数据类型)将图块 draw 在屏幕上。
requestIdleCallback
可能会被触发。这是作些非必要工做的好机会,好比标记分析数据。若是你不熟悉 requestIdleCallback
,Google Developers 上的入门知识能帮到你。在工做流程中深度的排序有两种版本。
首先是层叠上下文,好比有 2 个绝对定位的重叠的 div。更新图层树(Update Layer Tree) 是流程的一部分,保证 z-index
和相似的属性受到重视。
而后是合成图层,在上述流程较后的位置,多用于绘制元素。可使用空 transform 技巧(译注:指使用 translateZ(0,0)
强制开启硬件加速),或者 will-change: transform
将一个元素提高为合成图层,这样就能轻松地使用 transform 动画(有利于动画效果!)。可是若是存在重叠元素,浏览器也可能须要建立额外的合成图层,来保持由 z-index
或者其余属性指定的深度顺序。有趣!
实质上,上面概述的过程都是在 CPU 中完成的。只有最后一部分,图块被上传和移动的过程,是在 GPU 中完成的。
然而,在 Android 上,像素流在栅格化时有所不一样:GPU 用得更多一些。在 GPU 着色器上用 GL 命令执行 draw 调用,而不是在合成图块栅格化线程中进行栅格化。
这就是所谓的 GPU 栅格化,是一种下降绘制(paint)成本的方法。在 Chrome DevTools 中启用 FPS Meter(FPS 计数),你能够查看页面是否使用了 GPU 栅格化。
FPS 计数面板显示了正在使用 GPU 栅格化。
若是你但愿深刻研究,还有不少的资料,好比如何避免在主线程工做,或者浏览器渲染更深刻的运做机理。但愿这些资料能帮到你:
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。