经常被开发者问及像素渲染管道流的细节问题,它每一步是怎样触发的,何时触发的、为什么会触发。 这篇文章就是为了解答这些疑惑的,你将了解到像素是如何渲染到屏幕的。
注意:以下讲解基于 Blink 内核的 Chrome 浏览器。例如像layout 或 style calcs 等步骤运作在浏览器主线程中,这在大部分浏览器厂商中达成共识,但以下整体架构未必被其他厂商采纳。
一图胜千言
- 像素到渲染到屏幕的全过程

流程
在这张图里涉及到太多的内容,可以大致分为两个大的部分。 (以下文字需要反复对照这张图,为了方便,作者很贴心的给了图片地址The Anatomy of a Frame。)
- 渲染流程 Renderer Process
该流程包含许多线程负责处理不同的事物,确保让你的页面渲染到屏幕。 主要的线程有如下
- Compositor 合成线程
- Tile Worker 瓷砖工线程(每个像素就像瓷砖一块块叠加起来)
- Main threads 主线程
- GPU流程 GPU Process
该流程服务于上述的线程和浏览器其他的进程。当渲染流程处理完帧后,将触发 commit 事件。
GPU流程将瓷砖tiles 和其他数据(例如:quads and matrices)上传给 GPU,GPU 将像素输出到屏幕。
实际GPU流程包含一条单线程GPU Thread 负责与 GPU 通信。
渲染流程线程
现在分析下渲染流程中的线程。
- 合成线程 Compositor Thread.
当用户交互例如输入框输入数据,OS触发 vsync event 首先通知 Compositor Thread。
如果可以的话,该线程避免进入主线程,通过 GPU Thread 直接与 GPU 通信,把用户的交互行为渲染到屏幕上。
如果不可以(事件绑定了回调处理或其他可视化DOM操作),这时才需要主线程来处理。
- 主线程 Main Thread
该线程执行着我们耳熟能详的工作,如JavaScript, styles, layout 和 paint 等。
由于大量的工作运行在该线程中,该线程是造成页面卡顿jank的"真凶"。
- Compositor Tile Worker(s)
Compositor Thread 会生成(spawn)一条或多条子线程 Compositor Tile Worker 来处理光栅化任务。
PS:在某种程度上 Compositor Thread 才是 big boss ,虽然它不处理 JavaScript、Layout, Paint等任务。
但它全面的控制着主线程的初始化和把每帧运输到屏幕上的工作。
其实 Service Workers,Web Workers 也存在该过程中,只是有点复杂,暂且不表。
主线程
- 主线程全貌(这里把Parse HTML -> Composite 之间的过程称为渲染管道流)

接下来分析下图中完整版主线程中的每个事件是干什么工作的。
有一点需要记住的是:在实际中并不是每一步都会执行。
例如没有新的 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来优化性能。
LAYERS AND LAYERS
上面流程提及 layer 有两中概念,以示区分。
- Update Layer Tree 中的 Layer
层叠上下文the Stacking Contexts。例如两个绝对定位的div,根据元素前后出现和z-index会产生层叠关系。
这一过程在 Update Layer Tree 中处理,确保正确的层叠顺序。
- Compositor Layers 中的 Layers
这在之后的流程中处理,更适用于已绘制的元素的概念。
提升元素 Compositor Layer 层级的方法
will-change: transform/* transform 不支持的 hack */transform: translateZ(0) /* 3D */对于层级少animation元素能减少性能开销(避免主线程管道流中的某部分执行),俗称GPU加速。 但浏览器可能不得不创建额外的Compositor Layers来保存层叠顺序(z-index指定), 这就是产生了overlapping elements元素。
发散主题 RIFFING ON A THEME
上述主线程描述的过程差不多都发生在CPU中。只有最后一步,传输完后 tiles 在 GPU中处理。
然而在 Android 中有点不同,Compositor Tile Workers 光栅化 Rasterization 的操作,也在 GPU 中完成。
draw calls 作为 GL 命令在 GPU 着色器中执行。
这被称为 GPU Rasterization,这是一种减少 paint 成本的一种方式。
如果页面使用了GPU光栅化,能在Chrome DevTools 中的 FPS Meter 查看。
- FPS Meter

参考
如果你想学习如何避免卡顿 jank,想对性能优化方面有更高级的认知,
下面这些都能帮助你。
- Compositing in Blink & WebKit.A little old now, but still worth a watch.
- Browser Rendering Performance - Google Developers.
- Browser Rendering Performance - Udacity Course (totally free!).
- Houdini - The future, where you get to add more script to more parts of the flow.