上一篇导航流程讲到,网络进程请求到数据回来后,解析响应数据,并让浏览器进程通知渲染进程准备好,准备好后,浏览器向渲染进程发起提交文档的消息,渲染进程接受文档开始渲染。


渲染流程机制过于复杂,大致为以下渲染流水线

express 渲染页面 页面渲染流程_渲染引擎

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

1. 构建DOM树

浏览器无法识别HTML,所以得经过HTML解析器输出为dom树保存在内存结构中。

2. 样式计算

和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets

该阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存ComputedStyle 的结构内。

  • 样式属性值标准化,2em、blue、bold 类型数值不容易被渲染引擎理解,标准化为,2em 被解析成了 32px,red 被解析成了 rgb(255,0,0),bold 被解析成了 700
  • 计算出 DOM 树中每个节点的具体样式,继承规则,层叠规则

3. 布局阶段

  1. 创建布局树

    为了构建布局树,浏览器大体上完成了下面这些工作:
  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
  • 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。
  1. 布局计算
    接下来,就要计算布局树节点的坐标位置了,计算过程非常复杂,我们这里先跳过不讲

4. 分层

页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序,溢出裁切等,渲染引擎还需要为特定的DOM节点生成专用的图层,并生成一棵对应的图层树(LayerTree)

  • 拥有层叠上下文属性的元素会被提升为单独的一层。eg:position:fixed,z-index,opacity 等。
  • 需要剪裁(clip)的地方也会被创建为图层。

5. 图层绘制

渲染引擎会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表

express 渲染页面 页面渲染流程_chrome_02

6. 栅格化(raster)操作 (先划分为图块(tile),再栅格化)

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。

在有些情况下,有的图层可以很大,但是通过视口,用户只能看到页面的很小一部分,所以要绘制出所有图层内容的话,就会产生太大的开销,基于这个原因,合成线程会将图层划分为图块(tile)。

合成线程会按照视口附近的图块来优先生成位图(像素图),渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。

通常,栅格化过程都会使用 GPU 来加速生成,过程叫快速栅格化,生成的位图被保存在 GPU 内存中。GPU 操作是运行在 GPU 进程中,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。

渲染进程把生成图块的指令发送GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。

express 渲染页面 页面渲染流程_渲染引擎_03

7. 合成和显示

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


整个渲染流程 (发生在渲染进程中,主要分为两个线程)

express 渲染页面 页面渲染流程_chrome_04

总结:

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树。
  5. 为每个图层生成绘制列表,并将其提交到合成线程
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

相关概念

看看三个和渲染流水线相关的概念——“重排”“重绘”和“合成”。

  1. 重排 (更新了元素的几何属性)
  2. express 渲染页面 页面渲染流程_chrome_05

  3. 可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
  4. 重绘(更新元素的绘制属性)eg:背景颜色
  5. express 渲染页面 页面渲染流程_图层_06

  6. 重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
  7. 直接合成阶段
  8. express 渲染页面 页面渲染流程_渲染引擎_07

  9. CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。