渲染流水线

由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线,其大致流程如下图所示: image.png 按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

构建 DOM 树

为什么要构建 DOM 树呢?这是因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。

样式计算(Recalculate Style)

样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成。

1. 把 CSS 转换为浏览器能够理解的结构

image.png 从图中可以看出,CSS 样式来源主要有三种:

  • 通过 link 引用的外部 CSS 文件
  • 元素的 style 属性内嵌的 CSS

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

一个

2. 转换样式表中的属性值,使其标准化

现在我们已经把现有的 CSS 文本转化为浏览器可以理解的结构了,那么接下来就要对其进行属性值的标准化操作。 CSS 文本中有很多属性值,如 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

3. 计算出 DOM 树中每个节点的具体样式

  1. 首先是 CSS 继承。**CSS 继承就是每个 DOM 节点都包含有父节点的样式。**这么说可能有点抽象,我们可以结合具体例子,看下面这样一张样式表是如何应用到 DOM 节点上的。
  2. **层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。**它在 CSS 处于核心地位,CSS 的全称“层叠样式表”正是强调了这一点。

总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。

布局阶段

1. 创建布局树

在显示之前,我们还要额外地构建一棵只包含可见元素布局树。 image.png 依赖dom树以及样式计算属性构建布局树 为了构建布局树,浏览器大体上完成了下面这些工作:

  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
  • 而不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。

visibility: hidden的元素会出现在布局树中,这与display: none是不同的

2. 布局计算

分层

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。 通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。 创建新图层要求:

  • 拥有层叠上下文属性的元素会被提升为单独的一层。具有明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。
  • 需要剪裁(clip)的地方也会被创建为图层。出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。

剪裁:内容长度超出包裹元素的高度

图层绘制列表

渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表 image.png

栅格化(raster)操作

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系: image.png 内容过多要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。 基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512 image.png 然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示: image.png 通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。 image.png

合成和显示

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

渲染流水线总结

image.png

重排/回流

重排是指更新了元素的几何属性image.png 从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。 "回流"(reflow)是指在网页渲染过程中,当 DOM 元素的尺寸、位置或某些属性发生变化时,浏览器需要重新计算并重新渲染受影响的部分的过程。回流通常是由于以下操作引起的:

  • 修改了元素的尺寸、位置、内容等属性。
  • 修改了页面的布局、样式等属性。
  • 用户交互事件引起了元素尺寸、位置等的变化。

在前端开发中,因为回流会导致浏览器重新计算和渲染页面,所以会影响页面性能,特别是在元素频繁变化的情况下。为了减少回流次数,开发人员可以采用一些优化措施,如:

  • 避免频繁操作 DOM,尽量在一个时间段内集中修改多个元素。
  • 将频繁变化的元素设置为 position: absolute 或 fixed,这样它们不会影响其他元素的位置。
  • 避免通过 JavaScript 直接操作样式,尽量使用 class 名称来控制样式的变化。
  • 将元素的尺寸、位置等属性缓存起来,避免多次访问和计算。

重绘

重绘是指更新元素的绘制属性image.png

合成

直接合成阶段 那如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。具体流程参考下图: image.png

浙ICP备2021014928号 2023-PRESENT © Eric