渲染流水线

149 阅读4分钟

1. 构建 DOM 树。

将 HTML 文档解析成 DOM 树

2. 样式计算

2-1. 将 CSS 文本转换为浏览器可以识别的结构——styleSheets

在浏览器的控制台输入 document.styleSheets 可查看:

image.png

2-2. 标准化 CSS 属性值

image.png

2-3. 计算每个 DOM 节点具体的样式值

遵循继承和层叠规则

3. 布局阶段

3-1. 创建布局树

根据 DOM 树和样式计算的结果,生成一颗只含有可见元素的布局树。

image.png

3-2. 布局计算

计算布局树节点的坐标位置。

在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容。

4. 分层

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等。

为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree

在 Chrome 浏览器的开发者工具中,点击 Layers 选项可以查看当前网页的分层情况。

image.png

image.png

通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。

满足什么条件可以使渲染引擎为节点创建一个图层:

  1. 拥有层叠上下文属性的元素会被提升为单独的一层。 明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。

image.png

2.需要剪裁(clip)的地方也会被创建为图层。

剪裁是指,我们把 div 的大小限定为 200 * 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 * 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。

5. 绘制(生成绘制指令)

6. 分块

在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。基于这个原因,合成线程将图层划分为图块

7. 光栅化

合成线程会按照视口附近图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。

通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

8. 合成与显示

  1. 一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——DrawQuad
  2. 然后将该命令提交给浏览器进程
  3. 浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,
  4. 然后浏览器进程根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

总结:

image.png

补充:

重排触发重新布局,之后的流程都要进行。例如:修改元素的集合属性(宽、高)等。

重排需要更新完整的渲染流水线,所以开销也是最大的

重绘从绘制开始及其后的流程都要进行。例如:修改背景的颜色等。

相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些

合成:渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。例如:使用了 CSS 的 transform 来实现动画效果。

因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率