在上一模块中,我们探讨了关键渲染路径的一些理论,以及阻塞渲染和阻塞解析器的资源如何延迟网页的 初始渲染。现在,您已经了解了这方面的一些理论,接下来就可以学习一些优化关键渲染路径的技巧了。
网页加载时,其 HTML 中会引用许多资源,这些资源通过 CSS 为网页提供外观和布局,并通过 JavaScript 为网页提供互动性。在本模块中,我们将介绍与这些资源相关的一些重要概念,以及它们如何影响网页的加载时间。
阻塞渲染
如上一模块中所述,CSS 是一种阻塞渲染的资源, 因为它会阻止浏览器渲染任何内容,直到构建CSS 对象模型 (CSSOM)为止。浏览器会阻止渲染,以防止出现无样式内容闪烁 (FOUC),从用户体验的角度来看,这是不可取的。
在前面的视频中,您可以看到短暂的 FOUC,其中网页没有任何样式。随后,当网页的 CSS 从网络加载完毕后,所有样式都会应用,并且网页的无样式版本会立即替换为样式化版本。
一般来说,您通常不会看到 FOUC,但了解这个概念很重要,这样您就知道浏览器为什么会阻止网页的渲染,直到 CSS 下载并应用到网页为止。 阻塞渲染不一定是不好的,但您确实希望通过优化 CSS 来尽可能缩短阻塞渲染的时间。
阻塞解析器
阻塞解析器的资源会中断 HTML 解析器,例如没有 async 或 defer 属性的 <script>
元素。当解析器遇到
<script> 元素时,浏览器需要先评估并执行脚本,然后再继续
解析其余的 HTML。这是有意为之,因为脚本可能会在 DOM 仍在构建期间修改或访问 DOM。
<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>
当使用外部 JavaScript 文件(没有 async 或 defer,或者默认情况下为 defer 的 type=module)时,解析器会从发现文件到下载、解析和执行文件期间被阻塞。当使用内嵌 JavaScript 时,解析器也会被阻塞,直到内嵌脚本被解析和执行为止。
预加载扫描器
预加载扫描器是一种浏览器优化,以辅助 HTML 解析器的形式存在,它会扫描原始 HTML 响应,以便在主 HTML 解析器发现资源之前找到并推测性地提取资源。例如,预加载扫描器允许浏览器开始下载 <img> 元素中指定的资源,即使 HTML 解析器在提取和处理 CSS 和 JavaScript 等资源时被阻塞也是如此。
如需利用预加载扫描器,关键资源应包含在服务器发送的 HTML 标记中。预加载扫描器无法发现以下资源加载模式:
- CSS 使用
background-image属性加载的图片。这些图片引用位于 CSS 中,预加载扫描器无法发现它们。 - 动态加载的脚本,以
<script>元素标记的形式注入 到 DOM 中,使用 JavaScript 或使用 动态import()加载的模块。 - 使用 JavaScript 在客户端呈现的 HTML。此类标记包含在 JavaScript 资源中的字符串内,预加载扫描器无法发现它们。
- CSS
@import声明。
这些资源加载模式都是后期发现的资源,因此无法从预加载扫描器中受益。请尽可能避免使用它们。不过,如果无法避免使用此类模式,您可以尝试使用
preload 提示来避免资源发现延迟。
CSS
CSS 决定了网页的呈现方式和布局。如前所述,CSS 是一种阻塞渲染的资源,因此优化 CSS 可能会对整体网页加载时间产生相当大的影响。
缩减大小
缩减 CSS 文件大小可以减小 CSS 资源的文件大小,从而加快 下载速度。这主要是通过从源 CSS 文件中移除空格和其他不可见字符等内容,并将结果输出到新优化的文件来实现的:
/* Unminified CSS: */
/* Heading 1 */
h1 {
font-size: 2em;
color: #000000;
}
/* Heading 2 */
h2 {
font-size: 1.5em;
color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}
最基本的 CSS 缩减是一种有效的优化,可以改善网站的 FCP,在某些情况下甚至可以改善 LCP。捆绑器等工具可以在生产版本中自动为您执行此优化。
移除未使用的 CSS
在渲染任何内容之前,浏览器需要下载并解析所有样式表。完成解析所需的时间还包括当前网页上未使用的样式。如果您使用的是将所有 CSS 资源合并到单个文件中的捆绑器,则您的用户可能会下载比渲染当前网页所需的 CSS 更多。
如需发现当前网页未使用的 CSS,请使用 Chrome 开发者工具中的“覆盖率”工具。
移除 未使用的 CSS 具有双重效果:除了缩短下载时间外,您还可以优化 渲染树 构建,因为浏览器需要处理的 CSS 规则更少。
避免 CSS @import 声明
虽然看起来很方便,但您应避免在 CSS 中使用 @import 声明:
/* Don't do this: */
@import url('style.css');
与 HTML 中 <link> 元素的工作方式类似,CSS 中的 @import 声明
允许您从样式表中导入外部 CSS 资源。这两种方法的主要区别在于,HTML <link> 元素是 HTML 响应的一部分,因此比通过 @import 声明下载的 CSS 文件更早被发现。
原因是,为了发现 @import 声明,必须先下载包含该声明的 CSS 文件。 这会导致所谓的“请求链”,在 CSS 的情况下,这会延迟网页的初始渲染时间。 另一个缺点是,使用 @import 声明加载的样式表无法被预加载扫描器发现,因此会成为后期发现的阻塞渲染的资源。
<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">
在大多数情况下,您可以使用
<link rel="stylesheet"> 元素替换 @import。<link> 元素允许同时下载样式表,从而缩短整体加载时间,而 @import
声明则会连续下载样式表。
内嵌关键 CSS
下载 CSS 文件所需的时间可能会增加网页的 FCP。内嵌
关键样式在文档 <head> 中可以消除对
CSS 资源的网络请求,并且如果正确完成,则可以在用户浏览器缓存未准备就绪时缩短初始加载时间。其余 CSS 可以异步加载
,也可以附加到 <body> 元素的末尾。
<head>
<title>Page Title</title>
<!-- ... -->
<style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
<!-- Other page markup... -->
<link rel="stylesheet" href="non-critical.css">
</body>
不利的一面是,内嵌大量 CSS 会向初始 HTML 响应添加更多字节。由于 HTML 资源通常无法缓存很长时间(或根本无法缓存),这意味着内嵌 CSS 不会缓存到可能在外部样式表中使用了相同 CSS 的后续网页。请测试和衡量网页的性能,以确保权衡是值得的。
CSS 演示
JavaScript
JavaScript 驱动着网络上的大部分互动,但它是有代价的。 传送过多的 JavaScript 可能会导致网页在加载期间响应缓慢,甚至可能导致响应问题,从而减慢互动速度,这两种情况都会让用户感到沮丧。
阻塞渲染的 JavaScript
加载没有 defer 或 async 属性的 <script> 元素时,
浏览器会阻止解析和渲染,直到脚本下载、解析和
执行完毕。同样,内嵌脚本也会阻止解析器,直到脚本解析和执行完毕。
async 与 defer
async 和 defer 允许外部脚本加载,而不会阻止 HTML
解析器,同时具有 type="module" 的脚本(包括内嵌脚本)会自动
延迟。不过,async 和 defer 之间存在一些重要的区别。
使用 async 加载的脚本会在下载后立即解析和执行,而使用 defer 加载的脚本会在 HTML 文档解析完成后执行,这与浏览器的 DOMContentLoaded 事件同时发生。
此外,async 脚本可能会乱序执行,而 defer 脚本则会按照它们在标记中出现的顺序执行。
客户端渲染
一般来说,您应避免使用 JavaScript 渲染任何关键内容或一 页的 LCP 元素。这称为客户端渲染,是一种在单页应用 (SPA) 中广泛使用的技术。
JavaScript 渲染的标记会绕过预加载扫描器,因为客户端渲染的标记中包含的资源 无法被预加载扫描器发现。这可能会延迟关键资源(例如 LCP 图片)的下载。浏览器只有在脚本执行完毕并将元素添加到 DOM 后,才会开始下载 LCP 图片。反过来,脚本只有在被发现、下载和解析后才能执行。这称为关键请求 链,应避免使用。
此外,与从服务器下载以响应导航 请求的标记相比,使用 JavaScript 渲染标记更有可能生成 长任务。大量使用 客户端渲染 HTML 可能会对 互动延迟产生不利影响。如果网页的 DOM 非常大,则尤其如此,因为当 JavaScript 修改 DOM 时,会触发大量的渲染工作。
缩减大小
与 CSS 类似,缩减 JavaScript 大小可以减小脚本资源的文件大小。 这可以加快下载速度,让浏览器更快地进入解析和编译 JavaScript 的过程。
此外,缩减 JavaScript 大小比缩减其他资源(例如 CSS)更进一步。缩减 JavaScript 大小时,不仅会移除空格、制表符和注释等内容,还会缩短源 JavaScript 中的符号。此过程有时称为“丑化”。 如需了解其中的区别,请参阅以下 JavaScript 源代码:
// Unuglified JavaScript source code:
export function injectScript () {
const scriptElement = document.createElement('script');
scriptElement.src = '/js/scripts.js';
scriptElement.type = 'module';
document.body.appendChild(scriptElement);
}
如果对上述 JavaScript 源代码进行丑化,结果可能类似于以下代码段:
// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}
在上面的代码段中,您可以看到源代码中人类可读的变量
scriptElement 缩短为 t。当应用于大量脚本时,这种缩减可以节省大量空间,而不会影响网站生产 JavaScript 提供的功能。
如果您使用捆绑器处理网站的源代码,则通常会自动为生产版本执行丑化。丑化器(例如 Terser)也是高度可配置的,这让您可以调整丑化算法的激进程度,以实现最大的节省。不过,任何丑化工具的默认设置通常足以在输出大小和功能保留之间取得适当的平衡。
JavaScript 演示
知识测验
在浏览器中加载多个 CSS 文件的最佳方式是什么?
@import 声明。<link> 元素。浏览器预加载扫描器有什么用途?
<link rel="preload"> 元素。为什么浏览器在下载 JavaScript 资源时默认会暂时阻止解析 HTML?
接下来播放:使用资源提示帮助浏览器
现在,您已经了解了在 <head> 元素中加载的资源如何
影响初始网页加载和各种指标,接下来就可以继续学习了。在下一
模块中,我们将探讨资源提示,以及它们如何为
浏览器提供有价值的提示,以便浏览器比没有这些提示时更早地开始加载资源并打开与跨源
服务器的连接。