分析关键渲染路径性能

Ilya Grigorik
Ilya Grigorik

发布时间:2014 年 3 月 31 日

发现和解决关键渲染路径性能瓶颈需要充分了解常见的陷阱。您可以通过引导式导览来找出常见的性能模式,从而优化网页。

优化关键渲染路径能够让浏览器尽可能快地绘制网页:更快的网页渲染速度可以提高吸引力、增加网页浏览量以及提高转化率。为了最大程度减少访客看到空白屏幕的时间,我们需要优化加载的资源及其加载顺序。

为帮助说明这一流程,我们先从可能的最简单情况入手,逐步构建我们的网页,使其包含更多资源、样式和应用逻辑。在此过程中,我们将对每种情况进行优化;我们还会了解哪些方面可能存在问题

到目前为止,我们只关注了资源(CSS、JS 或 HTML 文件)可供处理后浏览器中会发生的情况,我们忽略了从缓存或网络获取资源所需的时间。我们假设:

  • 到服务器的网络往返(传播延迟时间)需要 100 毫秒。
  • HTML 文档的服务器响应时间为 100 毫秒,所有其他文件的服务器响应时间均为 10 毫秒。

Hello World 体验

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

试试看

从基本 HTML 标记和单个图像(无 CSS 或 JavaScript)开始。然后,在 Chrome DevTools 中打开“Network”面板,并检查生成的资源瀑布:

完整呈现率 (CRP)

不出所料,HTML 文件的下载时间约为 200 毫秒。请注意,蓝线的透明部分表示浏览器在不接收任何响应字节的情况下在网络上等待的时长,而实线部分表示的是在收到第一批响应字节后完成下载的时间。HTML 下载量很小 (<4K),因此我们只需单次往返便可获取整个文件。因此,获取 HTML 文档大约需要 200 毫秒,其中一半的时间花费在网络等待上,另一半花费在等待服务器响应上。

当 HTML 内容可用后,浏览器会解析字节,将它们转换成令牌,然后构建 DOM 树。请注意,为方便起见,开发者工具会在底部报告 DOMContentLoaded 事件的时间(216 毫秒),该时间也对应于蓝色垂直线。HTML 下载结束与蓝色垂直线 (DOMContentLoaded) 之间的间隔是浏览器构建 DOM 树所花费的时间 - 在本例中仅为几毫秒。

请注意,我们的“精彩照片”未阻止 domContentLoaded 事件。这证明,我们构建渲染树甚至绘制网页时无需等待页面上的每个资产:并非所有资源都对快速提供首次绘制具有关键作用。事实上,当我们谈论关键渲染路径时,通常谈论的是 HTML 标记、CSS 和 JavaScript。图片不会阻止网页的初始渲染,但我们也应设法尽快绘制这些图片。

即便如此,系统还是会阻止图像上的 load 事件(也称为 onload):DevTools 会在 335 毫秒时报告 onload 事件。回想一下,onload 事件标记了页面所需的所有资源均已下载和处理的时间点;此时,浏览器中的“正在加载”旋转图标会停止旋转(瀑布流中的红色竖线)。

结合使用 JavaScript 和 CSS

我们的“Hello World 体验”页面看上去很基本,但其背后蕴藏的却是许多任务。在实践中,我们还需要 HTML 之外的其他资源:我们可能需要 CSS 样式表以及一个或多个用于为网页增加一定交互性的脚本。将两者加入组合,看看会发生什么:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>

试试看

添加 JavaScript 和 CSS 之前:

DOM CRP

添加 JavaScript 和 CSS 后:

DOM、CSSOM、JS

添加外部 CSS 和 JavaScript 文件会向广告瀑布流添加两个额外的请求,浏览器差不多会同时发出这两个请求。但请注意,domContentLoaded 事件与 onload 事件之间的时间差现在小得多

发生了什么情况?

  • 与纯 HTML 示例不同,我们还需要获取并解析 CSS 文件才能构建 CSSOM,要想构建渲染树,DOM 和 CSSOM 缺一不可。
  • 由于该网页还包含一个阻止 JavaScript 文件的解析器,因此系统会屏蔽 domContentLoaded 事件,直到系统下载并解析 CSS 文件:因为 JavaScript 可能会查询 CSSOM,所以我们必须在下载 CSS 文件之前阻止这个 CSS 文件,然后才能执行 JavaScript。

如果我们用内联脚本替换外部脚本会怎样?即使直接将脚本内联到网页中,浏览器在构建 CSSOM 之前也无法执行它。简而言之,内联 JavaScript 也会阻止解析器。

不过,尽管内联脚本会阻止 CSS,但这样做是否能加快页面渲染速度呢?请尝试一下,看看会发生什么。

外部 JavaScript

DOM、CSSOM、JS

内嵌 JavaScript

DOM、CSSOM 和内嵌 JS

我们少发出了一个请求,但 onloaddomContentLoaded 时间实际上没有变化。为什么呢?我们知道,这与 JavaScript 是内联的还是外部的并无关系,因为只要浏览器遇到 script 标记,就会进行阻止,并等到 CSSOM 构建完毕。此外,在第一个示例中,浏览器会并行下载 CSS 和 JavaScript,并且大约会同时完成下载。在这种情况下,内联 JavaScript 代码并无多大意义。不过,有几种策略可以加快网页呈现速度。

首先回想一下,所有内联脚本都会阻止解析器,但对于外部脚本,我们可以添加 async 属性来解除对解析器的阻止。请撤消我们的内嵌并试一试:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>

试试看

阻止解析器的(外部)JavaScript:

DOM、CSSOM、JS

异步(外部)JavaScript

DOM、CSSOM、异步 JS

效果好多了!domContentLoaded 事件会在 HTML 解析完毕后不久触发;浏览器知道不要阻止 JavaScript,并且由于没有其他阻止解析器的脚本,CSSOM 构建也可以并行进行。

或者,我们也可以同时内联 CSS 和 JavaScript:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

试试看

DOM、内联 CSS、内联 JS

请注意,domContentLoaded 时间与前一示例中的时间实际上相同;只不过没有将 JavaScript 标记为异步,而是同时将 CSS 和 JS 内联到网页本身。这会使 HTML 页面更大,但好处是浏览器不必等待获取任何外部资源;页面上的所有内容

如您所见,即使是非常基础的页面,优化关键渲染路径也是一项非常重要的工作:我们需要了解不同资源之间的依赖关系图,需要确定哪些资源是“关键”资源,我们必须在网页中包含这些资源的不同策略中进行选择。此问题并非一成不变,每个网页都各不相同您需要遵循相似的流程,自行找到最佳策略。

不过,我们可以回过头来,看看能否找出某些常规性能模式。

性能模式

最简单的网页只包括 HTML 标记;没有 CSS,没有 JavaScript,也没有其他类型的资源。要渲染此类网页,浏览器需要发起请求,等待 HTML 文档到达,对其进行解析,构建 DOM,最后将其渲染在屏幕上:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

试试看

Hello World CRP

T0 到 T1 之间的时间是指网络和服务器处理时间。在最理想的情况下(如果 HTML 文件较小),我们只需一次网络往返便可获取整个文档。由于 TCP 传输协议工作方式的缘故,较大文件可能需要更多次的往返。因此,在最理想的情况下,上述网页具有单次往返(最少)关键渲染路径。

现在,我们还以同一网页为例,但这次使用外部 CSS 文件:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

试试看

DOM + CSSOM CRP

我们同样需要一次网络往返来获取 HTML 文档,然后检索到的标记告诉我们还需要 CSS 文件;这意味着,浏览器需要返回服务器并获取 CSS,然后才能在屏幕上渲染网页。因此,此网页至少需要两次往返才能显示出来。CSS 文件同样可能需要多次往返,因此重点在于“最少”。

以下是我们用来描述关键渲染路径的一些术语:

  • 关键资源:可能阻止网页首次渲染的资源。
  • 关键路径长度:获取所有关键资源所需的往返次数或总时间。
  • 关键字节数:完成网页首次渲染所需的字节总数,即所有关键资源的传输文件大小的总和。 我们的第一个示例包含一个 HTML 网页,其中包含一项关键资源(HTML 文档);关键路径长度也等于一次网络往返(假设文件较小),并且关键字节总数仅为 HTML 文档本身的传输大小。

现在,将其与上一个 HTML 和 CSS 示例的关键路径特性进行对比:

DOM + CSSOM CRP

  • 2 项关键资源
  • 2 次或更多次往返(最短关键路径长度)
  • 9 KB 的关键字节

我们需要 HTML 和 CSS 来构建渲染树。因此,HTML 和 CSS 都是关键资源:CSS 只有在浏览器获取 HTML 文档后才会提取,因此关键路径长度至少为两次往返。两项资源相加共计 9KB 的关键字节。

现在,将额外的 JavaScript 文件添加到组合中。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

试试看

我们添加了 app.js,它既是网页上的外部 JavaScript 资产,又是一种解析器阻止(即关键)资源。更糟糕的是,为了执行 JavaScript 文件,我们还需要进行阻止并等待 CSSOM;回想一下,JavaScript 可以查询 CSSOM,因此在下载 style.css 并构建 CSSOM 之前,浏览器将会暂停。

DOM、CSSOM、JavaScript CRP

即便如此,如果我们实际查看一下该网页的“网络瀑布”,就会注意到 CSS 和 JavaScript 请求差不多是同时发起的;浏览器获取 HTML,发现两项资源并发起两个请求。因此,上图中显示的网页具有以下关键路径特征:

  • 3 项关键资源
  • 2 次或更多次往返的最短关键路径长度
  • 11 KB 的关键字节

我们现在有三个关键资源,关键字节总计达 11KB,但是关键路径长度仍然是两次往返,因为我们可以并行传输 CSS 和 JavaScript。了解关键渲染路径的特征意味着能够确定关键资源,并且能够了解浏览器如何安排资源的抓取时间。

在与网站开发者交流后,我们发现我们在网页上加入的 JavaScript 不必具有阻止作用;其中包含一些无需阻止网页呈现的分析工具和其他代码。了解了这一点,我们就可以向 <script> 元素添加 async 属性来解除对解析器的阻止:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

试试看

DOM、CSSOM、异步 JavaScript CRP

异步脚本具有以下几个优点:

  • 脚本不再阻止解析器,也不再是关键渲染路径的组成部分。
  • 由于没有其他关键脚本,因此 CSS 不需要阻止 domContentLoaded 事件。
  • domContentLoaded 事件触发越早,其他应用逻辑开始执行的时间就越早。

因此,我们优化过的网页现在恢复到了具有两项关键资源(HTML 和 CSS),最短关键路径长度为两次往返,总关键字节数为 9 KB。

最后,如果 CSS 样式表只需用于打印,那会如何呢?

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

试试看

DOM、非阻塞 CSS 和异步 JavaScript CRP

因为 style.css 资源仅用于打印,浏览器无需阻止它即可呈现网页。因此,只要 DOM 构建完毕,浏览器便具有了渲染网页所需的足够信息。因此,该网页只有一项关键资源(HTML 文档),并且最短关键渲染路径长度为一次往返。

反馈