探讨如何消除渲染阻塞资源

1,736 阅读8分钟

缓慢的渲染时间也会让您的用户感到沮丧,并可能导致他们放弃您的页面。

1.8 秒对于 First Contentful Paint 很重要!
HTTP Archive从前约 700 万个 URL 收集数据。Lighthouse使用此数据来确定表现最佳的网站。然后将“绿色”级别设置在性能改进增加而收益递减的点上。

HTTP Archive(HTTP存档通过定期爬网web上的顶级站点来跟踪web是如何构建的,并记录有关获取的资源、使用的web平台API和特性的详细信息)

关键渲染路径是什么

我们在文件中编写 HTML、CSS 和 JavaScript,然后将这些文件传送到浏览器。浏览器将这些文件转换为您通过关键渲染路径看到的页面。步骤是:

  1. 下载HTML

  2. 阅读 HTML,同时:

    • 构建文档对象模型(DOM)
    • 注意<link>样式表的标签并下载CSS
  3. 阅读 CSS 并构建CSS 对象模型(CSSOM)

  4. 将 DOM 和 CSSOM 组合成一个渲染树

  5. 使用渲染树,计算布局(每个元素的大小和位置)

  6. 绘制或渲染页面上的像素

1.svg

什么是渲染阻塞资源

渲染阻塞资源是在关键渲染路径上“按暂停”的文件。它们会中断一个或多个步骤。

HTML 在技术上是渲染阻塞,因为您需要它来创建 DOM。如果没有 HTML,我们甚至都没有要呈现的页面。

然而,HTML 通常不是我们问题的原因......

CSS 是渲染阻塞。浏览器在创建 CSSOM 之前需要它,这会阻止所有后续步骤。一旦浏览器遇到样式表<link><style>标签,它就必须下载并解析内容。然后它必须在渲染的其余部分结束之后创建 CSSOM。您可以在图中的三角形点处看到这一点。在创建 CSSOM 和 DOM 之前,渲染树无法继续。

JavaScript 可以呈现阻塞。当浏览器遇到要同步运行的脚本时,它将停止 DOM 创建,直到脚本运行完毕。

2.svg 此外,如果 CSS 出现在脚本之前,则在创建 CSSOM 之前不会执行脚本。这是因为 JavaScript 也可以与 CSSOM 交互,我们不希望它们之间出现竞争条件。

3.svg CSS 会阻止脚本执行,而 JavaScript 会阻止 DOM 的构建!
注意: 图像和字体不会阻止渲染。

为什么它们对性能很重要

渲染阻塞资源可能会引发网络性能的一连串故障。第一次绘制变慢,这会导致最大内容绘制 (LCP) 变慢。LCP 是现在用于计算您的搜索引擎排名的核心 Web Vitals 之一。

搜索引擎优化对于发现您的网站很重要。性能对于将访问者留在您的页面上至关重要。如果页面未在 3 秒内加载,则页面放弃率会显着增加。

如何测试我的网站的渲染阻止资源?

如果您在 Lighthouse 中未能通过此指标,那么您已经找到了一种测试方法。

我们的网站上都有阻止渲染的资源(所有 CSS!)。当它显着影响我们的性能时,问题就会出现。发生这种情况时,Lighthouse 会对其进行标记,我们应该对此采取措施。

渲染阻塞资源的 Lighthouse 候选包括脚本和样式:

  • <script>中的标签<head>至少不具有以下属性之一:asyncdefer,module
  • 没有属性或不匹配的媒体查询<link>中的样式表标签(例如,)<head>``disabled``print

如果您未通过此指标,您的 Lighthouse 结果将如下所示:

4.png Lighthouse 列出了bootstrap、Google Fonts 样式表、JQuery 脚本。让我们再深入一些。让我们检查<head>对样品失效地点。它向我们展示了 2 个样式表,然后是 2 个脚本:

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
    integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i"
    rel="stylesheet">

  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
    integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous">
  </script>
  <script src="./jquery-3.5.1.js"></script>

  <title>Document</title>
</head>

Lighthouse 可以标记这 2 个初始样式表中的任何一个。这次失败的根本原因是:

  • 前 2 个样式表阻止 2 个同步脚本运行。浏览器必须首先下载样式表并创建 CSSOM。
  • 浏览器在下载、解析和执行这 2 个脚本之前无法构建 DOM 的其余部分。

另一种测试渲染阻塞资源的方法是使用WebPageTest。WebPageTest 是性能分析的下一步。如果您输入一个 URL,它将在真实的移动设备上运行性能测试。测试完成后,单击中间运行的瀑布。每个渲染阻塞资源都会有一个橙色圆圈,旁边有一个白色的 X

如何删除阻塞渲染的资源?

是时候填补那个漏洞并修复我们的网站了。让我们深入研究 CSS 和 JavaScript。我们的目标不是消除所有渲染阻塞资源,而是降低它们对性能的影响。Lighthouse 指标有助于确定您何时到达该点。

为关键渲染路径优化 CSS

对于我们的 CSS,我们希望

  1. 最小化我们的样式的大小
  2. 并提供给客户快速有效

我要问的第一个问题是:

我需要所有这些依赖项吗?

不要跳过第一个问题。最小化任何资产总大小的最简单方法是消灭它。把它夷为平地。例如,12 种 Google 字体似乎过多。我尽量将我的网络字体减少到最多 3-4 个样式集。

最小化 CSS 总量的下一个最简单的方法是最小化它。缩小是构建工具删除未使用的空白的过程。更少的字符 = 更小的尺寸 = 更快的下载。如果您使用的是 CSS 包,请确保使用它的缩小版本。下一步是使用构建工具自动最小化所有 CSS。示例工具包括 Gulp、Grunt、webpack 和 Parcel。SnowpackVite是有趣的新工具。

接下来,我会尝试将我的 CSS 分解成更小的块。理想情况下,我们只想交付我们将实际使用的 CSS。您可以看到有多少 CSS(和 JavaScript)实际上与Chrome Dev Tools 中的Coverage drawer一起使用。

  1. 打开开发工具
  2. 按 Cmd + Shift + P 打开快捷菜单
  3. 输入“Coverage”,然后选择“Show Coverage”

您可以单击重新加载按钮开始新的覆盖率分析。界面如下:

5.png

如果您的 CSS 总字节数很小,那么未使用的百分比就变得不那么重要了。例如,我的页面中有许多超过 90% 的未使用。因为我的 CSS 总量很小,所以我的 Lighthouse 分数仍然在 97-100 左右。

请注意,它仅适用于迄今为止使用的样式(或脚本)。当您交互或调整页面大小时,这些数字会上升。

您的目标不一定是 0% 未使用。但是,如果您看到大红条和大量未使用字节,则是时候减少初始渲染所需的 CSS。删除依赖项、使用代码拆分或内联关键 CSS 并推迟其余部分。

如果您有很多非屏幕样式,请考虑将它们提取到自己的样式表中。然后在<link>标签上使用媒体查询。例如:

<link href="styles/main.css" type="text/css" rel="stylesheet" media="screen">    
<!-- Only downloaded for print: -->  
<link href="styles/main_print.css" type="text/css" rel="stylesheet" media="print">

最后,不要@import在你的样式表中使用来加载更多的样式表。浏览器直到稍后才会发现它。最好<link>在 HTML 中使用标签加载它们。

针对关键渲染路径优化 JavaScript

正如我之前提到的,JavaScript 是解析器阻塞的。这意味着它会阻塞 DOM 构建,直到它完成执行。像 CSS 一样,我们想要:

  1. 最小化我们脚本的大小
  2. 并提供给客户快速有效

Coverage 抽屉还会分析您的脚本。您可以过滤 CSS 和 JavaScript 之间的结果。同样,要问自己的第一个问题是:

我需要所有这些依赖项吗?

JavaScript 是我们最昂贵的资产,而且容易膨胀。删除未使用的依赖项。此外,根据需要使用code-splitting(按需加载)、tree-shaking(按需引入)或延迟加载功能。您的构建工具是所有这些策略的朋友。

让我们谈谈如何有效地交付我们的 JavaScript。我见过的最好的理解 async vs defer vs module 的图表来自HTML 规范:

6.png

  • 无属性:在脚本下载和执行期间,HTML 解析器被阻止。
  • defer:HTML 解析器没有被阻止。浏览器在识别脚本时下载脚本。它仅在完成创建 DOM 后才执行脚本。
  • async:HTML 解析器在脚本执行期间被阻止。浏览器在识别脚本时下载脚本。下载后,脚本会阻塞 HTML 解析器,直到执行完成。
  • module:行为类似于 defer 但可以管理 ES6 模块导入。

做出明智的选择。在大多数情况下,您会想要deferasync优化关键渲染路径。如果您有一个必须同步运行的内联脚本,请测试将其移动到 HTML 中的样式上方。

结论

渲染阻塞资源会引发一系列性能问题。这些性能问题会导致不满意的用户更快地放弃您的页面。

Lighthouse 和 Coverage 工具可以帮助您识别此问题并评估您的最佳选择。我们学会了:

  • 减少我们的 CSS 和 JavaScript 字节,
  • 延迟加载非关键 CSS 和 JavaScript,以及
  • 使用deferasyncmodule对我们的脚本属性。