当一个浏览器接收到从服务器发来的 html 页面,在渲染并呈现到屏幕上之前,有很多步骤要做。浏览器渲染页面需要做的一系列行为被称作 “关键渲染路径(Critical Rendering Path 简称 CRP)”。
CRP
的知识对于如何提升网站性能是相当有用的。 CRP
有 6 个步骤:
- 构建 DOM 树
- 构建 CSSOM 树
- 运行 JavaScript
- 创建渲染树
- 生成布局
- 绘制页面
# 构建 DOM 树
DOM(Document Object Model)树是一个表示整个解析过的 HTML 页面的对象,从根节点 <html>
开始,会创建页面中的每个 元素 / 文本 节点。嵌套在其他元素中的元素作为字节点,每个节点都包含了其所有的元素属性,例如: 一个 <a>
节点将有 href
属性与其关联。
举个例子
<html> | |
<head> | |
<title>Understanding the Critical Rendering Path</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<header> | |
<h1>Understanding the Critical Rendering Path</h1> | |
</header> | |
<main> | |
<h2>Introduction</h2> | |
<p>Lorem ipsum dolor sit amet</p> | |
</main> | |
<footer> | |
<small>Copyright 2017</small> | |
</footer> | |
</body> | |
</html> |
上面的 HTML 将会被解析成下面的 DOM 树
HTML 的优点在于它不必等待整个页面加载完成才呈现页面,可以解析一部分,显示一部分。但是像 CSS、JavaScript 等其他资源会阻止页面渲染。
# 构建 CSSOM 树
CSSOM(CSS Object Model) 是一个跟 DOM 相关的样式对象。它跟 DOM 的表示方法是相似的,但是不论显式声明还是隐式继承,每个节点都存在关联样式。
In the style.css file from the document mentioned above, we have the folowing styles
在上面提到的 html 页面的 style.css
中的样式如下
body { font-size: 18px; } | |
header { color: plum; } | |
h1 { font-size: 28px; } | |
main { color: firebrick; } | |
h2 { font-size: 20px; } | |
footer { display: none; } |
它会被构建成下面的 CSSOM 树
CSS 被认为是 “渲染阻塞资源”,它意味着如果不首先完全解析资源,渲染树是无法构建的。CSS 由于它的层叠继承的性质,不能像 HTML 一样解析一部分,显示一部分。定义在文档后面的样式会覆盖或改写之前定义的样式,因为在整个样式表都被解析之前,如果我们使用了在样式表中较早定义的样式,那错误的样式将被应用。这意味着 CSS 必须被全部解析之后,才能开始下一步。
如果 CSS 文件适用于当前设备的话,仅仅只是会阻塞渲染。 <link rel="stylesheet">
标签可以使用 media
属性,用来指定特定样式宽度的特定媒体查询。
举个例子,如果我们有一个包含媒体属性 orientation:landscape
的样式,我们使用纵向模式(portrait mode)查看页面,这个资源将不会阻塞渲染。
CSS 也会导致脚本阻塞。这是因为 JavaScript 文件必须等待 CSSOM 被构建后才能运行。
# 运行 JavaScript
JavaScript 被认为是 解析阻塞资源
,这意味着 HTML 的解析会被 JavaScript 阻塞。
当解析器解析到 <script>
标签时,无论该资源是内部还是外链的都会停止解析,先去下载资源。这也是为什么,当页面内有引用 JavaScript 文件时,引用标签要放到可视元素之后了。
为避免 JavaScript 解析阻塞,它可以通过设定 async 属性来要求其异步加载。
<script async src="script.js">
# 创建渲染树
渲染树是 DOM 和 CSSOM 的结合体,它代表最终会渲染在页面上的元素的结构对象。这意味着它只关注可见内容,对于被隐藏或者 CSS 属性 display:none 的属性,不会被包含在结构内。
使用上面例子的 DOM 和 CSSOM,渲染树被创建如下:
# 生成布局
布局决定了浏览器视窗的大小,它提供了上下文依赖的 CSS 样式,如百分比或窗口的单位。视窗尺寸通常通过 <head>
标签中的 <meta>
中的 viewport 设定来决定。如果不存在该标签,则通常默认为 980px
例如,最常用的 meta veiwport
的值将会被设置为和设备宽度相符:
<meta name="viewport" content="width=device-width,initial-scale=1">
如果用户访问网页的设备宽度为 1000px。然后整体视窗尺寸就会基于这个宽度值了,比如 50% 就是 500px, 10vw 就是 100px 等等。
# 绘制页面
最后,在绘制页面步骤。页面上的所有可见内容都会被转换为像素并呈现在屏幕上。
具体的绘制时间跟 DOM 数以及应用的样式有关。有些样式会花费更多的执行时间,比如复杂的渐变背景图片所需要的计算时间远超过简单固定背景色。
# 整合所有
想要看到关键渲染路径的执行流程,可以使用 DevTools,在 Chrome 中,它是根据时间轴展示的。
举个例子,上面的页面加入 <script>
标签
<html> | |
<head> | |
<title>Understanding the Critical Rendering Path</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<header> | |
<h1>Understanding the Critical Rendering Path</h1> | |
</header> | |
<main> | |
<h2>Introduction</h2> | |
<p>Lorem ipsum dolor sit amet</p> | |
</main> | |
<footer> | |
<small>Copyright 2017</small> | |
</footer> | |
<script src="main.js"></script> | |
</body> | |
</html> |
可以看关于页面加载时的事件日志,以下是我们获得的:
- Send Request - 发送到 index.html 的 GET 请求
- Parse HTML and Send Request - 开始解析 HTML 并构建 DOM,然后发送 GET 请求 style.css 和 main.js
- Parse Stylesheet - 根据 style.css 创建的 CSSOM
- Evaluate Script - 执行 main.js
- Layout - 基于 HTML 的元视窗标签,生成布局
- Paint - 绘制网页
基于这些信息,我们可以知道如何优化关键渲染路径。
原文: Understanding the Critical Rendering Path