浏览器中页面的渲染流程
构建Dom树
浏览器无法直接理解和使用HTML,所以需要将HTML转换为浏览器能够理解的结构——DOM树
样式计算
1.将CSS转换为StyleSheets
<link href="style.css" rel="stylesheet">
<style>
div {
color: red;
}
</style>
<p style="color: red"></p>css 来源基本有三种
- link 标签链接的外部样式表
- style 标签中的 css 样式
- 元素行内 style 属性设置的行内样式
和HTML文件一样,浏览器也是无法直接理解这些纯文本的CSS样式,所以当渲染引擎接收到CSS文本时,会执行一个转换操作
2.转换样式表中的值,标准化
body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
/* 转换后 */
body { font-size: 32px }
p {color:rgb(0,0,255);}
span {display: none}
div {font-weight: 700}比如 em、bold、blue 这样的值,进行属性标准化,转换成浏览器认识的值
3.计算Dom树中每个节点样式
首先是 CSS继承。CSS继承就是每个DOM节点都包含有父节点的样式。 然后是 CSS 的层叠(Cascading)指的是多个 CSS 规则应用到同一个元素时,根据规则的特定性和来源的顺序确定哪些样式会被应用,并以何种方式应用这些样式。
- 样式特定性:具有更高特定性的规则将覆盖具有较低特定性的规则。特定性可以通过选择器中的元素、类、ID 以及内联样式来确定。通常,内联样式具有最高的特定性,其次是 ID 选择器,然后是类和伪类选择器,最后是元素选择器。
- 源代码顺序:当存在多个相同特定性的规则时,以最后出现的规则为准。换句话说,后面的规则将对前面的规则进行覆盖。
- 重要性:使用 "!important" 声明的样式具有最高的优先级,将覆盖其他所有规则。
布局
创建布局树
在DOM树还含有很多不可见的元素,比如head标签,还有使用了 display:none 属性的元素。所以在显示之前,还要额外地构建一棵只包含可见元素布局树。
- 遍历DOM树中的所有可见节点,并把这些节点加到布局中;
- 而不可见的节点会被布局树忽略掉,如head标签下面的全部内容、使用了 display:none 属性的元素。
布局计算
分层
因为页面中有很多复杂的效果,如一些复杂的3D变换、页面滚动,或者使用z-indexing做z轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树LayerTree)
Chrome的“开发者工具”,选择“Layers”标签,就可以可视化页面的分层情况
浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。
图层和布局树节点之间的关系:
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。如上图中的 span 标签没有专属图层,那么它们就从属于它们的父节点图层。但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。
通常满足这两点中任意一点的元素就可以被提升为单独的一个图层。
第一点,拥有层叠上下文属性的元素会被提升为单独的一层。
比如明确定位(position)属性的元素、定义透明(opacity)属性的元素、使用CSS滤镜(filter)的元素等,都拥有层叠上下文属性。
第二点,需要剪裁(clip)的地方也会被创建为图层。
比如div的大小限定为200 * 200像素,而div里面的文字内容比较多,文字所显示的区域肯定会超出200 * 200的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在div区域。 出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。
图层绘制
完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制。
渲染引擎实现图层的绘制中会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。
“开发者工具”的“Layers”标签,选择“document”层,可以查看绘制列表
栅格化(raster)操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。
通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(viewport)。
在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是256x256或者512x512,然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。
所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行.
通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。

合成和显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。 浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
渲染流水线总结
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上

相关
更新了元素的几何属性(重排)
例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。重排需要更新完整的渲染流水线,所以开销也是最大的。
更新元素的绘制属性(重绘)
如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
直接合成阶段
使用了CSS的transform来实现动画效果,避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。