当浏览器从服务器接收到页面的 HTML 响应时,在屏幕上绘制像素之前需要执行很多步骤。浏览器需要为页面的初始绘制运行的这个序列称为“关键渲染路径”。
CRP 的知识对于了解如何改进站点的性能非常有用。CRP 有 6 个阶段 -
- 构建 DOM 树
- 构建 CSSOM 树
- 运行 JavaScript
- 创建渲染树
- 生成布局
- 绘画
1. 构建 DOM 树
DOM(文档对象模型)树是完全解析的 HTML 页面的对象表示。从根元素开始,<html>
为页面上的每个元素/文本创建节点。嵌套在其他元素中的元素表示为子节点,每个节点都包含该元素的完整属性。例如,一个<a>
元素将具有href
与其节点关联的属性。
以这个示例文档为例 -
<span style="background-color:#3c3c3c"><span style="color:#ffffff"><code class="language-html"><span style="color:#ffdb3a"><span style="color:#ffdb3a"><html</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><head</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><title</span>></span>Understanding the Critical Rendering Path<span style="color:#ffdb3a"><span style="color:#ffdb3a"></title</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><link</span> <span style="color:#f28596">rel</span><span style="color:#f28596">="stylesheet"</span> <span style="color:#f28596">href</span><span style="color:#f28596">="style.css"</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></head</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><body</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><header</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><h1</span>></span>Understanding the Critical Rendering Path<span style="color:#ffdb3a"><span style="color:#ffdb3a"></h1</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></header</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><main</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><h2</span>></span>Introduction<span style="color:#ffdb3a"><span style="color:#ffdb3a"></h2</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><p</span>></span>Lorem ipsum dolor sit amet<span style="color:#ffdb3a"><span style="color:#ffdb3a"></p</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></main</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><footer</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><small</span>></span>Copyright 2017<span style="color:#ffdb3a"><span style="color:#ffdb3a"></small</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></footer</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></body</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></html</span>></span>
</code></span></span>
这将创建以下 DOM 树 -
HTML 的一个好处是它可以部分执行。无需加载完整文档即可开始在页面上显示内容。但是,其他资源(CSS 和 JavaScript)可能会阻止页面的呈现。
2. 构建 CSSOM 树
CSSOM(CSS 对象模型)是与 DOM 关联的样式的对象表示。它以与 DOM 类似的方式表示,但包含每个节点的关联样式,无论它们是显式声明的还是隐式继承的。
在上述style.css
文档的文件中,我们有以下样式 -
<span style="background-color:#3c3c3c"><span style="color:#ffffff"><code class="language-css"><span style="color:#ffdb3a">body</span> { <span style="color:#f28596">font-size</span>: 18px; }
<span style="color:#ffdb3a">header</span> { <span style="color:#f28596">color</span>: plum; }
<span style="color:#ffdb3a">h1</span> { <span style="color:#f28596">font-size</span>: 28px; }
<span style="color:#ffdb3a">main</span> { <span style="color:#f28596">color</span>: firebrick; }
<span style="color:#ffdb3a">h2</span> { <span style="color:#f28596">font-size</span>: 20px; }
<span style="color:#ffdb3a">footer</span> { <span style="color:#f28596">display</span>: none; }
</code></span></span>
这将创建以下 CSSOM 树 -
CSS 被认为是“渲染阻塞资源”。这意味着如果不首先完全解析资源,就无法构建渲染树(见下文) 。与 HTML 不同,CSS 不能在部分中使用,因为它具有继承级联的性质。文档中稍后定义的样式可以覆盖和更改先前定义的样式。因此,如果我们在整个样式表被解析之前就开始使用样式表中之前定义的 CSS 样式,我们可能会遇到应用了错误 CSS 的情况。这意味着在我们进入下一个阶段之前必须完全解析 CSS。
CSS 文件仅在应用于当前设备时才被视为渲染阻塞。<link rel="stylesheet">
标签可以接受一个media
属性,我们可以在其中指定样式适用的任何媒体查询。例如,如果我们有一个具有媒体属性的样式表orientation:landscape
,并且我们正在以纵向模式查看页面,则该资源将不会被视为渲染阻塞。
CSS 也可以是“脚本阻塞”。这是因为 JavaScript 文件必须等到 CSSOM 构建完成后才能运行。
3. 运行 JavaScript
JavaScript 被认为是“解析器阻塞资源”。这意味着 HTML 文档本身的解析被 JavaScript 阻止了。
当解析器到达一个<script>
标签时,无论是内部的还是外部的,它都会停止获取(如果它是外部的)并运行它。这就是为什么如果我们有一个引用文档中元素的 JavaScript 文件,它必须放在该文档出现之后。
为了避免 JavaScript 被解析器阻塞,它可以通过应用async
属性异步加载。
<span style="background-color:#3c3c3c"><span style="color:#ffffff"><code class="language-js"><span style="color:#67b3dd"><</span>script <span style="color:#ffdb3a">async</span> src<span style="color:#67b3dd">=</span>"script.js"<span style="color:#67b3dd">></span>
</code></span></span>
4. 创建渲染树
渲染树是 DOM 和 CSSOM 的组合。它是一棵树,表示最终将在页面上呈现的内容。这意味着它只捕获可见内容,而不包括例如使用 CSS 隐藏的元素display: none
。
使用上面的示例 DOM 和 CSSOM,将创建以下渲染树 -
5. 生成布局
布局决定了视口的大小,它为依赖于它的 CSS 样式提供上下文,例如百分比或视口单位。视口大小由文档头中提供的元视口标签决定,如果没有提供标签,则使用默认的视口宽度 980px。
例如,最常见的元视口值是将视口大小设置为与设备宽度相对应 -
<span style="background-color:#3c3c3c"><span style="color:#ffffff"><code class="language-html"><span style="color:#ffdb3a"><span style="color:#ffdb3a"><meta</span> <span style="color:#f28596">name</span><span style="color:#f28596">="viewport"</span> <span style="color:#f28596">content</span><span style="color:#f28596">="width=device-width,initial-scale=1"</span>></span>
</code></span></span>
如果用户在宽度为 1000 像素的设备上访问网页,则尺寸将基于该单位。一半的视口为 500px,10vw 为 100px,以此类推。
6. 绘画
最后,在绘画步骤中,可以将页面的可见内容转换为要在屏幕上显示的像素。
绘制步骤所需的时间取决于 DOM 的大小以及应用的样式。有些样式比其他样式需要更多的工作来执行。例如,复杂的渐变背景图像将比简单的纯色背景颜色需要更多时间。
把它们放在一起
要查看进程中的关键渲染路径,我们可以在 DevTools 中检查它。在 Chrome 中,它位于Timeline选项卡下(在 Canary 中,即将成为 Chrome 稳定版,它被重命名为Performance)。
以我们上面的示例 HTML 为例(带有添加的<script>
标签) -
<span style="background-color:#3c3c3c"><span style="color:#ffffff"><code class="language-html"><span style="color:#ffdb3a"><span style="color:#ffdb3a"><html</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><head</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><title</span>></span>Understanding the Critical Rendering Path<span style="color:#ffdb3a"><span style="color:#ffdb3a"></title</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><link</span> <span style="color:#f28596">rel</span><span style="color:#f28596">="stylesheet"</span> <span style="color:#f28596">href</span><span style="color:#f28596">="style.css"</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></head</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><body</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><header</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><h1</span>></span>Understanding the Critical Rendering Path<span style="color:#ffdb3a"><span style="color:#ffdb3a"></h1</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></header</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><main</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><h2</span>></span>Introduction<span style="color:#ffdb3a"><span style="color:#ffdb3a"></h2</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><p</span>></span>Lorem ipsum dolor sit amet<span style="color:#ffdb3a"><span style="color:#ffdb3a"></p</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></main</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><footer</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><small</span>></span>Copyright 2017<span style="color:#ffdb3a"><span style="color:#ffdb3a"></small</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></footer</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"><script</span> <span style="color:#f28596">src</span><span style="color:#f28596">="main.js"</span>></span><span style="color:#ffdb3a"><span style="color:#ffdb3a"></script</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></body</span>></span>
<span style="color:#ffdb3a"><span style="color:#ffdb3a"></html</span>></span>
</code></span></span>
如果我们查看页面加载的事件日志,这就是我们得到的 -
- 发送请求- 为 index.html 发送 GET 请求
- Parse HTML and Send Request - 开始解析 HTML 和 DOM 构造。为 style.css 和 main.js 发送 GET 请求
- 解析样式表- 为 style.css 创建的 CSSOM
- 评估脚本- 评估 main.js
- 布局- 基于 HTML 中的元视口标签生成布局
- Paint - 在文档上绘制像素
基于这些信息,我们可以决定如何优化关键渲染路径。我将在以后的文章中介绍其中的一些技术。