原文连接git
翻译自:The Anatomy of a Frame github
常常被开发者问及像素渲染管道流的细节问题,它每一步是怎样触发的,什么时候触发的、为何会触发。 这篇文章就是为了解答这些疑惑的,你将了解到像素是如何渲染到屏幕的。web
注意:如下讲解基于 Blink
内核的 Chrome
浏览器。例如像layout
或 style calcs
等步骤运做在浏览器主线程中,这在大部分浏览器厂商中达成共识,但如下总体架构未必被其余厂商采纳。canvas
在这张图里涉及到太多的内容,能够大体分为两个大的部分。 (如下文字须要反复对照这张图,为了方便,做者很贴心的给了图片地址The Anatomy of a Frame。)api
该流程包含许多线程负责处理不一样的事物,确保让你的页面渲染到屏幕。 主要的线程有以下浏览器
该流程服务于上述的线程和浏览器其余的进程。当渲染流程处理完帧后,将触发 commit
事件。 GPU流程将瓷砖tiles
和其余数据(例如:quads and matrices)上传给 GPU
,GPU
将像素输出到屏幕。 实际GPU流程包含一条单线程GPU Thread
负责与 GPU
通讯。性能优化
如今分析下渲染流程中的线程。多线程
当用户交互例如输入框输入数据,OS触发 vsync event
首先通知 Compositor Thread
。 若是能够的话,该线程避免进入主线程,经过 GPU Thread
直接与 GPU
通讯,把用户的交互行为渲染到屏幕上。 若是不能够(事件绑定了回调处理或其余可视化DOM操做),这时才须要主线程来处理。架构
该线程执行着咱们耳熟能详的工做,如JavaScript
, styles
, layout
和 paint
等。 因为大量的工做运行在该线程中,该线程是形成页面卡顿jank
的"真凶"。app
Compositor Thread
会生成(spawn)一条或多条子线程 Compositor Tile Worker
来处理光栅化任务。
PS:在某种程度上 Compositor Thread
才是 big boss
,虽然它不处理 JavaScript
、Layout
, Paint
等任务。 但它全面的控制着主线程的初始化和把每帧运输到屏幕上的工做。
其实 Service Workers
,Web Workers
也存在该过程当中,只是有点复杂,暂且不表。
接下来分析下图中完整版主线程中的每一个事件是干什么工做的。 有一点须要记住的是:在实际中并非每一步都会执行。 例如没有新的 HTML
须要解析,Parse HTML
就不会执行。 前面提到的卡顿的优化,就是避免渲染管道中的事件反复执行或避免某部分的执行。 具体参考渲染性能优化的最佳实践课程。
一样值得注意的是:styles
和 layout
底下的红色箭头指向 requestAnimationFrame
。 requestAnimationFrame
能确保在一帧(实际上是指渲染管道流)开始前执行完毕。 但若是你在 requestAnimationFrame
代码不注意的话,可能会触发styles,layout
的提早执行, 致使强制同步重排/布局 Forced Synchronous Layout
,极大的破坏页面性能。
PS:之前的一些库动画实现例如 jQuery
依赖 setTimeout
或 setInterval
,能够的话尽可能用 CSS3
和 requestAnimationFrame
替代。
Frame Start
:Vsync
触发, 一帧开始。Input event handlers
:合成线程 compositor thread
把 input
数据传给主线程, 处理事件回调。OS调度程序将尽最大努力合理调度事件回调(touchmove, scroll, click等),以及时响应用户交互。 即使如此,在用户交互和主线程处理事件得到响应之间多少会有些延迟。requestAnimationFrame
由于它离vsync
很近,能够就近获取input data
。因此这是操做dom
理想的地方,例如修改 100
个元素的 class
, 并不会致使100
次样式计算style calculations
,而是会在以后的管道流中批量处理。 须要注意的是:你不能在查询任何已计算的样式或布局属性(例如el.style.backgroundImage
, el.style.offsetWidth
)。 若是你这么作了,就像图中红色箭头标明的同样,recalc styles
或 layout
或二者会提早执行,将致使强制布局,更糟会引发页面抖动。 Avoid Large, Complex Layouts and Layout Thrashing。Parse HTML
:任何新增的 HTML
都会被处理,构建新的DOM
元素。 你能够在页面加载或 appendChild
等操做中看到这一过程。Recalc Styles
:对于解析样式文件或class
或 style
等样式操做都会引起样式计算。可能会从新渲染整棵样式树。 具体取决于哪一个元素的样式改变,例如 body
就影响比较大。值得注意的是,浏览器已经很聪明能自动限制波及的范围。Layout
:计算可见元素几何(盒模型)信息(位置、尺寸)。一般会对整个文档操做一遍。 产生的开销和 DOM
个数成正比例关系。Update Layer Tree
:建立层叠上下文和元素层级顺序。Paint
:其实这是绘画的第一步,这一步记录须要调用绘制的方法 draw calls
(fill a rectangle here, write text there)。 第二步是光栅化 Rasterization
(下面会提到),draw calls
会被执行。 第一步显然速度要快于第二步,但常常把这两步都成为 painting
。Composite
:层 layer
和瓷砖 tile
信息被计算后回传给 compositor thread
处理。 Composite
负责处理有 will-change
,overlapping elements
,或任何硬件加速的 canvas
。Raster Scheduled and Rasterize
:光栅调度和光栅化,这里将执行在 Paint
任务中提到的draw calls
。 将在 Compositor Tile Workers
中处理,该线程的多少取决于系统和硬件设备的能力。 例如 Android
一般起一个Compositor Tile Workers
线程,PC 可能有4个。 根据层 layers
信息来光栅化,layers
是由不少瓷砖 tiles
组成。Frame End
:全部 layers
中被光栅化的 tiles
和 input data
(可能被事件回调处理了)将被提交给 GPU Thread
。Frame Ships
:最后,全部的瓷砖 tiles
都将被 GPU Thread
上传给硬件 GPU
处理。 GPU
将使用quads and matrices
来把 tiles
打印在屏幕上。requestIdleCallback
在主线程处理完一帧后还剩余一些处理空间的话 requestIdleCallback
会被触发。 这是一个很是好的机会来处理非必要的工做,如用户行为信息等采集。 若是你是一个新手,这里有份参考:Using requestIdleCallback。
值得一提,最新的 React
利用此api来优化性能。
上面流程说起 layer
有两中概念,以示区分。
层叠上下文the Stacking Contexts
。例如两个绝对定位的div
,根据元素先后出现和z-index
会产生层叠关系。 这一过程在 Update Layer Tree
中处理,确保正确的层叠顺序。
这在以后的流程中处理,更适用于已绘制的元素的概念。 提高元素 Compositor Layer
层级的方法
will-change: transform
/* transform 不支持的 hack */transform: translateZ(0) /* 3D */
对于层级少 animation
元素能减小性能开销(避免主线程管道流中的某部分执行),俗称 GPU加速
。 但浏览器可能不得不建立额外的 Compositor Layers
来保存层叠顺序(z-index指定), 这就是产生了 overlapping elements
元素。上述主线程描述的过程差很少都发生在CPU
中。只有最后一步,传输完后 tiles
在 GPU
中处理。
然而在 Android
中有点不一样,Compositor Tile Workers
光栅化 Rasterization
的操做,也在 GPU
中完成。 draw calls
做为 GL 命令在 GPU
着色器中执行。
这被称为 GPU Rasterization
,这是一种减小 paint
成本的一种方式。 若是页面使用了GPU光栅化,能在Chrome DevTools
中的 FPS Meter
查看。
若是你想学习如何避免卡顿 jank
,想对性能优化方面有更高级的认知, 下面这些都能帮助你。