【浏览器原理-4】浏览器的页面渲染
课程链接:https://time.geekbang.org/column/intro/100033601?tab=catalog
渲染的整体流程
渲染流程是从 HTML 到页面的流程。由于它的机制太过于复杂,我们把这样的一个处理流程叫做渲染流水线。按照时间顺序,我们把渲染流水线大致分为:
- 构建 DOM 树
- 样式计算
- 布局
- 分层
- 绘制
- 分块
- 光栅化
- 合成
构建 DOM 树
服务器返回的 HTML 是一段字符串,浏览器是无法理解和使用的。这时候,需要将 HTML 转换成浏览器能够理解的结构——DOM 树。
样式计算
和 HTML 一样,CSS 也是无法被浏览器直接理解的,所以首先需要将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
控制台输入
document.styleSheets可以查看它的结构
此外,由于 CSS 存在各种不同的单位,如 px、em、rem、%等,为了方便浏览器解析和处理,需要将这些单位转换成统一的单位,这个过程称为 标准化。
接下来,需要算出每个 DOM 节点的样式属性,计算过程需要遵守 CSS 的继承规则和层叠规则。
布局
浏览器执行到这一步时,已经拥有了 DOM 结构以及各个节点的样式。由于 DOM 结构上面有很多不显示的元素,如 head 标签和含有 display: none 样式的元素。为了方便绘制界面,需要将这些不显示的元素删掉,这就又创建了一颗 布局树 。
创建好布局树后,浏览器便会对布局树上所有的节点进行布局计算。计算结果会重新写入布局树中。
这里并没有严格区分输入和输出与。Chrome 团队正在重构布局代码,下一代布局系统叫 LayoutNG,试图更清晰地分离输入和输出,从而让新设计的布局算法更加简单。
分层
由于浏览器页面具有复杂的效果,如页面滚动和一些复杂的 3D 效果,以及 z-index带来的一些层叠效果。所以,浏览器还需要为一些特殊的节点生成专用的图层,并生成对应的图层树。浏览器最后展示的页面,是多个图层叠加的结果。
Chrome Devtools 可以通过“图层”选项卡来浏览网页中的图层

需要注意的是,不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
- 拥有层叠上下文属性的元素会被提升为单独的一层.明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。
- 第二点,需要剪裁(clip)的地方也会被创建为图层。(例如限定文本宽高然后设置
overflow: auto;,这种情况会为文字创建一个单独的层,如果有滚动条,滚动条也会成为一个单独的层)
绘制
构建完图层树之后,渲染引擎会对图层树中的每个图层进行绘制。为了方便,渲染引擎会把图层的绘制分成小的绘制指令(如 绘制正方形等)。
在 Chrome Devtools 中的 Layers 也可以看到具体的绘制指令的一些信息。

光栅化
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
通常,一个页面是很大的,用户只能看见一部分,用户能看见的这一部分称为 视口(Viewport)。所以,一开始就绘制整个页面是比较浪费的。
此时,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512。
然后合成线程会优先生成视口附近的位图。
所谓栅格化,是指将图块转换为位图,而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。
栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。GPU 操作是是在专门的 GPU 进程中完成的。
合成
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令—— DrawQuad,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存的页面内容显示在屏幕上。
渲染流程小结
| 阶段 | 输入 | 处理 | 产物 |
|---|---|---|---|
| 构建 DOM 树 | HTML 字符串 | 转换 | DOM 树 |
| 样式计算 | CSS | 生成 styleSheets、标准化、计算各个节点的样式 | 各个 DOM 节点的样式 |
| 布局 | DOM 树 | 去除不显示的元素 | 布局树 |
| 分层 | 布局树 | 针对特殊的元素进行分层 | 分层树 |
| 绘制 | 分层树 | 生成绘制指令 | 绘制列表 |
| 分块 | 绘制列表 | 合成线程将图层分成图块 | 图块 |
| 光栅化 | 图块 | 在光栅化线程池中将图块转换成位图 | 位图 |
| 合成 | 位图 | 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上 | 页面 |
浏览器页面的更新
重排
如果通过 JavaScript 或者 CSS 修改元素的几何位置属性(元素的宽度、高度),那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。
重绘
如果只是单纯的修改了元素的背景颜色,那么布局阶段会被跳过,直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。
直接合成阶段
如果使用 CSS 的 transform 来实现动画效果,就可以避开重排和重绘阶段,直接在非主线程上执行分块、栅格化、合成等操作。
性能的影响
很明显,重排最耗费性能,重绘其次,直接合成是性能影响最小的。
- 标题: 【浏览器原理-4】浏览器的页面渲染
- 作者: ObjectKaz
- 创建于: 2021-12-22 14:27:36
- 更新于: 2021-12-27 14:34:39
- 链接: https://www.objectkaz.cn/89a86982d1ff.html
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。