[译] 理解关键渲染路劲

584 阅读3分钟

浏览器在使用从服务端接收到的HTML响应文件在屏幕上绘制成像素之前还有很多步骤。浏览器在初始绘制页面所做这一系列操作称之为“关键渲染路径(Critical Rendering Path)简称CRP”。

学习了CRP相关的知识会对如何提升网站性能有非常大的帮助。要完成CRP有以下6个步骤:

  1. 构建DOM树(作者的关于DOM讲解的另一篇文章可以看这里
  2. 构建CSSOM树
  3. 运行Javascript
  4. 创建渲染树
  5. 生成布局
  6. 绘制

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, 会得到以下信息 -

  1. Send Request - 获取请求得到的index.html
  2. Parse HTML and Send Request - 开始解析HTML和构造DOM。发送请求获取style.css和main.js
  3. Parse Stylesheet - 通过style.css创建CSSOM
  4. Evaluate Script - 估算 main.js
  5. Layout - 使用HTML生成基于meta标签视窗的布局
  6. Pain - 将文档绘制成像素

基于以上信息我们就可以对怎么优化关键渲染路径展开讨论。我会在后面的一些技术文章讲到