- 原文作者:Ire Aderinokun
浏览器在使用从服务端接收到的HTML响应文件在屏幕上绘制成像素之前还有很多步骤。浏览器在初始绘制页面所做这一系列操作称之为“关键渲染路径(Critical Rendering Path)简称CRP”。
学习了CRP相关的知识会对如何提升网站性能有非常大的帮助。要完成CRP有以下6个步骤:
- 构建DOM树(作者的关于DOM讲解的另一篇文章可以看这里)
- 构建CSSOM树
- 运行Javascript
- 创建渲染树
- 生成布局
- 绘制
1. 构建DOM树
DOM(文档对象模型)是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>
这会创建如下的DOM树 -
HTML的优点在于它能够部分执行。所有的文档内容没有必要在一开始的时候就全部加载到页面上。然而,像某些资源,比如CSS和Javascript就会阻塞页面的渲染。
2. 构建CSSOM树
CSSOM(CSS对象模型)是和DOM相关联的样式的一种对象表现形式。虽然它的呈现形式和DOM很类似,但是它还包含和每个节点相关联的样式,无论是显示声明的还是隐式继承的。
使用之前的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样式。这意味着在进行下一个步骤之前CSS必须全部解析完毕。
CSS设备只有应用在当前设备上才会被视为阻塞渲染。<link rel="stylesheet">
标签接受一个media
属性,我们可以指定任何想要运用的媒体查询属性样式。例如,我们在一个样式表里面将media
的属性设为为orientation:landscape
,而我们此时正在以纵向模式查看页面,那么这个资源就不会被视为阻塞渲染资源。
CSS也可以是"脚本阻塞"。这是因为Javascript文件必须要在CSSOM已经构建完成之后才能运行。
3. 运行Javascript
Javascript被看做是一种“解析阻塞资源”。这意味着解析HTML文档本身就是被Javascript阻塞的。
当解析器遇到<script>
标签,无论是内部的还是外部的,它都会停止获取(如果它是外部的)并且运行它。这就是为什么如果我们在一个文档内部引入了Javascript文件,那么它必须放置在那个文档后面。
为了避免Javascript被解析器阻塞,可以通过设置async
属性来实现异步加载。
<script async src="script.js">
4. 创建渲染树
渲染树是DOM和CSSOM的组合。它是一种用于表示最终在页面上渲染哪些元素的树形数据结构。这意味着它只会捕捉到可见的内容,而过滤掉不可见的,例如,使用CSS设置了display:none
属性的而隐藏的元素。
使用上述例子中提到的DOM和CSSOM,可以创建以下的渲染树 -
5. 生成布局
布局是决定视窗大小的要素,它能为视窗提供CSS样式所依赖的上下文,例如:百分比或者视窗单元。视窗的大小通过文档头部的meta
标签来决定,或者如果没有meta
标签,视窗的宽度会使用默认的980px。
比如,最常使用的通过meta
设置视窗的值来适配不同设备的宽度 -
<meta name="viewport" content="width=device-width,initial-scale=1">
假如用户使用一个宽度为1000px的设备访问web页面,那么页面就是使用1000px作为基准。视窗的一半就是500px,10vw就是1000px,以此类推。
6. 绘制
最后,在绘制阶段,页面上的可见元素会被转换成像素展示在屏幕上。
绘制阶段花费的时间取决于DOM的大小和应用在DOM上面的样式。某些复杂样式相比于其他简单样式需要更多的时间来处理它。例如,background-image
是一个复杂的渐变属性,它就会比一个简单的背景色样式花费更多的时间。
整合
为了看清楚关键渲染路径这个过程,我们要在开发者工具中观察它。在Chrome中,它在Timeline这个选项卡下面(在很快会成为Chrome稳定版的Canary里面,它被重命名为Performance,(此处就是指的开发者工具的Performance))
来看我们之前的样例HTML(添加了一个<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>
如果我们看一下页面加载的Event Log, 会得到以下信息 -
- Send Request - 获取请求得到的index.html
- Parse HTML and Send Request - 开始解析HTML和构造DOM。发送请求获取style.css和main.js
- Parse Stylesheet - 通过style.css创建CSSOM
- Evaluate Script - 估算 main.js
- Layout - 使用HTML生成基于
meta
标签视窗的布局 - Pain - 将文档绘制成像素
基于以上信息我们就可以对怎么优化关键渲染路径展开讨论。我会在后面的一些技术文章讲到