在上一篇文章中,我们看到了导航阶段。三个流程协同工作以交付下一阶段的文档。 在这篇文章中,我们将打开渲染阶段的盒子,看看里面有什么。
从文档到网页 --- 8 个子阶段
渲染阶段包括8个子阶段:
- DOM构建 ( DOM construction )
- 样式计算 ( Style computation )
- 布局 ( Layout )
- 层 ( Layer )
- 画 ( Paint )
- 分块 ( Tiling )
- 光栅化 ( Raster )
- 绘制显示 ( Draw Quad and Display )
渲染器进程中的两个线程涉及:
- 主线程负责 1-5 个阶段
- 合成器线程管理 6-8 个阶段
第一阶段:DOM构建
输入是从网络过程中传递过来的 HTML 文档,输出是 DOM 树。 为什么是 DOM 树?浏览器不支持 HTML。渲染器进程首先需要将 HTML 文档"翻译"为浏览器可以理解的内容。 那就是 DOM 树。 让我们看一个例子。
html
<html><body>
<h1>Hello world!</h1>
<div>
<p>
It is a message from
<span>the web</span>
</p>
</div>
</body></html>
该示例的 DOM 树如图所示。 DOM 树是 HTML 代码的表示。我们可以通过在 Chrome 控制台中输入"文档"来访问它。
DOM 树存在于内存中。因此,JavaScript 可以访问并编辑它。
第 2 阶段:风格计算
输入是 CSS 样式,输出是计算的样式结构。 它分为三个步骤。
第 1 步:"翻译"CSS
与 HTML 类似,浏览器不支持 CSS。翻译的结果是样式表。 在Chrome浏览器控制台中输入"document.styleSheets",我们可以看到所有解析的样式表。 这些样式表来自
- 标签中链接的来源,
- 内的样式,以及
- 内联样式
与 DOM 树一样,样式表看起来像 JavaScript 对象结构,可以在内存中访问和编辑。
我们以Medium主页为例。它有 11 个样式表。第一个来自 标记,其余的都是 标记内的样式。
步骤 2:标准化值和统一
我们在 CSS 中使用各种值和单位。
- 宽度:50%
- 填充:2em 0
- 字体大小:1rem
它们是相对值。 在此步骤中,所有相对值都将转换为绝对值(像素)。 为什么是像素?在渲染阶段结束时,浏览器在屏幕上显示位图。位图由像素组成。 是时候做一下我们的数学计算了。 标准化结束后,之前的 CSS 值更改为以下内容:
- width: 500px (假设其父元素的宽度为1000px。)
- padding: 32px 0 (假设元素的字体大小为16px。)
- font-size: 16px (假设根元素的字体大小为16px。)
第三步:样式计算
最后,渲染器进程计算计算出的样式并将它们附加到每个元素。 为什么要计算最终样式 ( computed style )? CSS 代表层叠样式表。级联意味着一个元素从其祖先继承一些样式,并用自己的样式覆盖其中的一部分。 渲染器进程根据级联规则计算所有样式。然后它为每个元素生成最终计算样式的列表。 例如,一个元素从其祖先继承 font-size: 16px。本身有一个font-size:32px。计算出的 font-size 样式为 font-size: 32px。
该图显示了 Medium 主页上 h2 元素的计算样式列表。 计算完成后,最终结果是计算出的样式列表。我们可以在Chrome控制台中查看一下。
第三阶段:布局 ( Layout )
在此阶段,输入是 DOM 树和计算样式。输出是带有计算的布局信息的布局树。
步骤一:构建布局树 ( Layout Tree )
布局树看起来就像 DOM 树的复制品。 有什么区别?首先,DOM 树中"不可见"的元素不会包含在布局树中。 例如,具有 display: none 的元素会从布局树中排除。与
标记内的所有元素相同。有一个例外:可见性为隐藏的元素;位于布局树中。 其次,一些 CSS 属性将内容添加到布局中。 以伪类为例,div::after {content: 'I'm here';} 创建了一个包含在布局树中的内容,尽管它不存在于 DOM 树中。
步骤2:计算几何信息
要在空白画布上画一个盒子,我们需要知道:
- 起始 x、y 坐标,以及
- 盒子的尺寸
布局树中的每个元素都是一个盒子。 计算每个元素的最终几何形状是一项艰巨的工作。想一想。在 CSS 中,许多属性都会修改元素的几何形状。
- float: left
- width: 100px
- display: absolute
- ...
设计一个有效的布局系统来使用它是具有挑战性的。如果您有兴趣,BlinkOn 的开发人员与我们分享了一些知识。 主线程为我们完成繁重的工作并完成所有的计算。 现在它有一个布局树。在布局树上,每个元素都有精确的坐标和大小信息。
第四阶段:层 ( Layer )
输入是布局树,输出是图层树。 第三层是关于层的。 我们都喜欢 CSS 中的 3D 变换效果和方便的 z-index 属性。它们都与图层相关。 如果您曾经使用过 Photoshop 或 Sketch,您就会对图层非常熟悉。浏览器中的层具有相同的概念。 同时,图层树与绘画顺序有关。为什么我们要关心绘画顺序? 例如,您有两个重叠元素,元素 A 和元素 B。A 的 z-index: 9,比没有 z-index 属性的 B 更重要。 A 将显示在 B 的顶部。渲染器进程首先在画布上绘制 B,然后在其顶部绘制 A。这是一个绘画订单。 绘制顺序对于网页的准确显示至关重要。 图层树是什么样子的?
从图中我们可以看到并非所有元素都有自己的图层。绘制层价格昂贵。为了获得更好的性能,渲染器进程仅在需要时创建图层。 什么时候需要?渲染器进程考虑两种元素。
具有堆叠上下文的元素
- z-index
- position: absolute
- transform
- will-change
以上是一些与堆叠上下文相关的 CSS 属性。渲染器进程为具有任何这些属性的元素创建一个层。 您可以在 MDN 文档上查看影响图层树的 CSS 属性的完整列表。 Chrome 允许我们查看网页上的图层。
剪裁的元素
剪切元素的典型情况是溢出的文本元素。 假设我们有一个 300px * 300px 的 div。 div 框中的长文本溢出。当给盒子溢出时:auto;我们知道文本在 div 内可以滚动。 当看到溢出的文本元素时,渲染器进程会创建三层。
- 一种用于剪切文本,在 300px * 300px 区域中可见。
- 其中一个用于全文内容,以便您可以滚动浏览它们。
- 一个用于滚动条
在控制台中查看更容易理解这个机制。 在 Chrome 中运行以下 HTML。
html
<html>
<body>
<h1>Hell world!</h1>
<div class="box" style="width: 300px; height: 300px; overflow: auto;">
<p>Lorem ipsum vel viverra elementum ut et parturient placerat curae at vestibulum ullamcorper a ullamcorper mattis nascetur ullamcorper ut mollis rhoncus sed parturient in vestibulum vestibulum. Leo nec nisi nec erat a purus aliquam habitasse a sem id nisi ullamcorper viverra suspendisse mollis fringilla a dis a suspendisse parturient a. A commodo scelerisque mi dictum non orci suspendisse felis velit dapibus a sit facilisis velit mi libero ultrices leo consectetur tempus montes quis consequat condimentum a ullamcorper amet. Donec orci fames id consectetur nascetur parturient et magnis nunc lacinia aliquet nec dolor dictumst conubia a hac nibh consectetur habitant vel a cras.</p>
</div>
</body>
</html>
打开图层面板看一下。图层显示在图层面板中。尝试滚动框中的文本,您就会明白。
最后,在完成所有计算之后,渲染器进程手头上就有了图层树。
第五阶段:绘制 ( Paint )
在此阶段,输入是布局树和图层树,输出是绘制记录。 绘制记录是什么样的? 它由三个部分组成:
- Action
- 位置,包括坐标(x,y)和尺寸(宽度,高度)。
- 样式
例如,绘制记录如下所示:
- Action: Draw Rect
- Pos: 0, 0, 300, 300
- backgroundColor: red
在(0, 0)处绘制一个300px * 300px的红色矩形是一个绘制记录。 绘画记录就像浏览器执行绘画的注释。 绘画记录是这些注释的列表,按照我们在图层树中确认的顺序排列,例如"首先是背景,然后是矩形,然后是文本"。 在下一步中,合成器线程继承主线程的工作,开始根据绘制记录生成位图。
第六阶段:分块 ( Tiling )
输入是绘制记录和图层,输出是图块。 平铺时,图层被分成图块,浏览器根据视口位置优先进行渲染。 这里有三个关键词:layer、tile、viewport。 我们以 Medium 主页为例。从 DevTools 的 Layer 面板中,我们可以看出它分为三层:
- document
- header
- scroll bar
"文档"层被分成多个图块。通常,图块尺寸为 256px * 256px 或 512px * 512px。
渲染所有图块的成本很高。当前位于视口中的可见图块具有优先权。
第 7 阶段:光栅化 ( Raster )
输入是图块,输出是位图 ( bitmaps )。 了解图块后,合成器线程创建一个光栅线程池。多个光栅线程同时进行光栅图块。
为了加速该过程,光栅线程通过 IPC 将图块发送到 GPU 进程。然后 GPU 进程从图块生成位图并将位图保存在 GPU 内存中。 当位图准备好后,GPU 进程将它们传递回渲染进程中的合成器线程以进行下一步。
第 8 阶段:绘制显示 ( Draw Quad and Display )
输入是位图,输出是合成器帧。 当处理完所有需要的图块后,合成器线程会向浏览器进程发送一个名为"绘制四边形"的命令。 在浏览器进程内部,"viz"组件接收"绘制四边形"命令,执行"显示合成器"命令,并将合成器帧"绘制"到我们的计算机内存中。
最后,浏览器进程在浏览器中显示合成器框架。 令人印象深刻的是,在现代浏览器中,整个 8 个阶段都在 1/60 秒内发生。即 16.67 毫秒。
要点
渲染的8个阶段:
- 渲染器进程中的主线程将 HTML 文档"翻译"为 DOM 树
- 它将 CSS 传递到计算样式中
- 它基于 DOM 树和计算样式创建布局树
- 从布局树中,它生成图层树
- 最后,建立绘制记录
- 合成器线程继承绘制记录,根据当前视口开始耕种
- 多个光栅线程在 GPU 进程的帮助下将光栅图块转换为位图。
- 浏览器进程接收来自浏览器进程的Draw Quad命令,然后它在我们面前显示页面的一个框架。
如果您听说过渲染树,那么它在 2019 年就不再相关了。这里类似的概念是图层树。