当一个浏览器接收到从服务器发来的 html 页面,在渲染并呈现到屏幕上之前,有很多步骤要做。浏览器渲染页面需要做的一系列行为被称作 “关键渲染路径(Critical Rendering Path 简称 CRP)”。

CRP 的知识对于如何提升网站性能是相当有用的。 CRP 有 6 个步骤:

  1. 构建 DOM 树
  2. 构建 CSSOM 树
  3. 运行 JavaScript
  4. 创建渲染树
  5. 生成布局
  6. 绘制页面
    CRP的6个步骤

# 构建 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 树
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 树
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>

可以看关于页面加载时的事件日志,以下是我们获得的:
时间轴

  1. Send Request - 发送到 index.html 的 GET 请求
  2. Parse HTML and Send Request - 开始解析 HTML 并构建 DOM,然后发送 GET 请求 style.css 和 main.js
  3. Parse Stylesheet - 根据 style.css 创建的 CSSOM
  4. Evaluate Script - 执行 main.js
  5. Layout - 基于 HTML 的元视窗标签,生成布局
  6. Paint - 绘制网页
    基于这些信息,我们可以知道如何优化关键渲染路径。

原文: Understanding the Critical Rendering Path