当一个浏览器接收到从服务器发来的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 - 绘制网页
基于这些信息,我们可以知道如何优化关键渲染路径。