渲染流程 + 重排重流合并
输入的HTML + CSS + JS,最后输出像素,我们把这样的一个处理流程叫做渲染流水线。一个完整的渲染流水线大致可总结为如下:
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
简单概括为:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。
1. 构建DOM树
构建 DOM 树的输入内容是一个非常简单的 HTML 文件,然后经由 HTML 解析器解析,最终输出树状结构的 DOM.
2. 样式计算: 计算每个元素的具体样式
计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成
- 渲染引擎会把获取到的 CSS 文本全部转换为
styleSheets
,该结构同时具备了查询和修改功能,为后面的样式操作提供基础。控制台中输入document.styleSheets
可看
转换样式表中的属性值,使其标准化
计算出 DOM 树中每个节点的具体样式
3. 创建布局树
在显示之前,我们还要额外地构建一棵只包含可见元素布局树。遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中。
4. 布局阶段
4.1 分层
因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树。
浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。
渲染引擎才会为特定的节点创建新的图层呢?
- 定位属性的元素
- 定义透明属性的元素、
- 使用 CSS 滤镜的元素
- z-index元素
- 需要裁剪的地方(出现滚动条)
4.2 图层绘制
渲染引擎会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。
4.3 栅格化
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。
栅格化过程都会使用 GPU 来加速生成
4.4 合成与显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
6. 重排 - 更新元素的几何属性
通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,这个过程叫重排。重排需要更新完整的渲染流水线,所以开销也是最大的
7. 重绘 - 更新元素的绘制属性
那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
8. 直接合成阶段
那如果你更改一个既不要布局也不要绘制的属性。渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。
我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。