查看原文
编程

【第3036期】Fetch Priority 和优化LCP

吴彦祖火星分祖 前端早读课 2023-08-23

前言

介绍了 Fetch Priority API 以及如何通过使用 fetchpriority 属性和优化 LCP(Largest Contentful Paint)来指示资源的优先级。Fetch Priority API 允许开发者向浏览器指示资源的相对优先级。同时介绍了不同浏览器对资源的优先级分配策略,以及如何使用 preconnect 和 preload 指令来优化资源加载。

今日前端早读课文章由 @吴彦祖火星分祖翻译授权分享。

正文从这开始~~

Fetch Priority API 用于向浏览器指示资源的相对优先级。您可以通过在 <img><link><script> 和 <iframe> 元素上添加 fetchpriority 属性,或通过 Fetch API 上的 priority 属性来配置优先级。

浏览器的加载过程是复杂的。浏览器主要通过请求的类型和在文档标记中的位置来确定其优先级。例如,在文档的 <head> 中请求的 CSS 文件将被分配最高优先级,而带有 defer 属性的 <script> 元素将被分配低优先级。浏览器按照发现资源的顺序下载具有相同优先级的资源。

fetchpriority

fetchpriority 属性可用于提示浏览器提高或降低所请求资源的优先级。具有以下三个属性值:

  • high - 相对于默认优先级,该资源更重要

  • low - 相对于默认优先级,该资源较不重要

  • auto - 默认值

<img src="/lcp.jpg" alt="A dog" fetchpriority="high" />

在上面的示例中,我们提示浏览器将 <img> 的优先级设为比默认优先级更重要。

在 fetch 方法的 priority 属性也支持相同的值。

fetch("/api/data.json", { priority: 'high' })

在上述的 fetch 请求中,我们向浏览器表明该 fetch 请求的优先级比默认优先级要高。

默认优先级

Fetch Priority API 可以提高或降低资源相对于其默认的优先级。例如,图像在默认情况下始终以 low 优先级开始,但分配 fetchpriority="high" 会将它们的优先级提高到 high。另一方面,阻塞渲染的样式表默认分配了 Highest 优先级。分配它 fetchpriority="low" 会将其优先级降低到 high - 但不是 low。fetchpriority 用于调整资源相对于其默认值的优先级,而不是显式设置其值。

在 influence of Fetch Priority on resource prioritization in Chromium 文档中记录了不同的资源类型、它们的默认优先级 (◉) 以及使用 fetchpriority="high"(⬆) 和 fetchpriority="low"(⬇) 时产生的优先级。

请注意,如果发现图像在视口内,则其优先级会提高到 High。但是,这可能在加载过程中靠后的阶段发生,如果请求已经发送则可能影响很小或没有影响。使用 fetchpriority="high" 允许您告诉浏览器以 High 优先级启动,而不是等待浏览器确定它是否在视口中。

“紧凑模式”(Tight mode)

大多数浏览器分两个阶段下载资源。在初始阶段(Chromium 也将此称为 “Tight mode”),浏览器不会下载 Low 优先级资源,除非少于两个正在传输的请求。

在上面的瀑布图中,您可以看到资源 image-1.jpg 即使它立即被发现,也会等到 style-2.css 完成下载后才开始下载。此时,只有 script.js 一个资源仍在传输中,这时浏览器开始下载 Low 优先级图像。

一旦 <head> 中的所有阻塞脚本都已下载并执行(带有 async 或 defer 的脚本不呈现阻塞),初始阶段就完成了。即使有两个以上正在传输的请求,浏览器现在也可以根据它们的优先级和它们在标记中出现的顺序继续下载任何剩余的资源。

在上图中,一旦阻塞渲染的 JavaScript 下载并执行(粉色条),即使这两个 CSS 文件仍在传输中,浏览器也会开始下载图像。黄色垂直线表示 DOM 可交互或者 readystatechange 事件被触发的时间。

preconnect

如果图像位于不同的域上,则浏览器需要在下载文件之前打开与该域的连接。

这显示在 WebPageTest 图表上,下载前有绿色、橙色和洋红色条。我们可以使用 preconnect 资源提示更早地开始下载图像。

在上图中,与 cdn.glitch.global 域的连接在初始阶段打开 - 在浏览器能够开始下载文件之前。一旦浏览器退出初始阶段(黄色垂直线),它就会立即开始下载图像 —— 节省大约 350 毫秒。

preload

如果我们能够使用 preconnect 资源提示缩短下载时间,我们是否能够使用 preload 指令进一步缩短下载时间?简洁的回答:不能。preload 指令允许您通知浏览器有关 “晚发现” 的关键资源。这对于在样式表或脚本中加载的资源特别有用,例如背景图或字体。在我们的示例中,图像在标记中声明并较早发现,因此预加载几乎没有影响。

在上图中,我们已将 preconnect 替换为以下内容:

<link
rel="preload"
as="image"
href="https://cdn.glitch.global/.../image-1.jpg"
/>

尽管有预加载,图像仍然不会开始下载,直到少于两个正在运行的请求。

fetchpriority

我们可以使用 Fetch Priority 向浏览器表明 image-1.jpg 比其默认优先级更重要,使用:

<img
src="https://cdn.glitch.global/.../image-1.jpg"
fetchpriority="high"
alt=""
/>

这应该会将图像的初始优先级从 Low 增加到 High,使得图像能在初始阶段被处理。

上面的瀑布图显示, image-1.jpg 在初始阶段与其他关键资源同时被处理。这给了我们迄今为止最大的改进。

Firefox

Firefox 使用类似的启发式方法来确定应在初始阶段加载哪些资源。然而,与基于 Chromium 的浏览器不同,它不会开始下载任何 Low 优先级资源,直到 <head> 中的所有 JavaScript 都被下载并执行 - 即使只有一个 High 优先级请求正在运行。

以上截图来自 Firefox Web 开发者工具,显示在下载和执行脚本(第 2 行)以及页面变得可交互之后,图片资源(第 5 至 8 行)被获取的时间点,用蓝色的垂直线表示。

当 Chrome 等待在 <head> 中声明的 JavaScript 下载并执行时,Firefox 等待所有在图像元素之前声明的渲染阻塞 JavaScript—— 即使这些是在 <head> 之外声明的。

Firefox 还不支持 fetchpriority ,但是,我们可以使用 preload 指令提高 image-1.jpg 的优先级。

在上面的屏幕截图中,文件 image-1.jpg 与其他资源并行获取。这类似于我们在 Google Chrome 上添加 fetchpriority="high" 时看到的行为。

Safari

iOS 和 macOS 上的 Safari 也有一个初始阶段,尽管它的行为不同于 Chrome 和 Firefox。

低优先级资源在正在传输中的请求少于两个时开始获取。这不依赖于 readystatechange 事件,即使在没有任何阻止渲染的 JavaScript 的页面上,浏览器也会等待至少有一个正在传输中的请求。

上图显示了从 Safari 的 Web Inspector 中获取的截图,直到 style-1.css 完成下载并且正在传输中的请求少于两个时,图像才开始下载。

在 Safari 上,初始阶段仅适用于同源资源。如果 Low 优先资源是从不同的域加载的,它们将在被发现后立即获取。

在上面的截图中,跨域图像被立即获取,而不是等待 High 优先级资源完成下载。

proload 指令不会影响资源的优先级。然而,将 <link rel="preload"> 指令放置在高优先级请求之前会导致它更早地下载;因为在发现时正在传输中的请求少于两个。这与其他浏览器上的行为相同,在大多数情况下,我建议不要将 proload 指令放置在高优先级资源之上,因为阻塞渲染的 CSS 应该具有优先权。

此图中,Low 优先级文件 image-1.jpg 在 High 优先级 style-1.css 文件之前开始下载,因为在文档标记中 <link rel="preload"> 位于其上方。

将 preload 与 fetchpriority 组合

到目前为止,Fetch Priority 仅在基于 Chromium 的浏览器上受支持,但是,它在不识别 fetchpriority 属性的浏览器上会进行降级处理。这允许我们将 preload 指令与 Fetch Priority 结合起来。

【第1053期】Preload,Prefetch 和它们在 Chrome 之中的优先级

<link
rel="preload"
as="image"
fetchpriority="high"
href="https://cdn.glitch.global/.../image-1.jpg"
/>

支持 Fetch Priority 的浏览器将使用分配的 fetchpriority 预加载资源,而不支持的浏览器将使用 preload 指令。

上图表显示了与之前在 <img> 元素上包含 fetchpriority 属性的图表类似的结果。这种方法的优势在于统一处理资源的优先级,无论是在支持 Fetch Priority 的浏览器上还是在不支持的浏览器上。

fetchpriority 的潜在好处

在本节中,我们将研究使用 fetchpriority 的潜在好处。所有数据均取自 HTTP Archive,我们仅考虑使用 HTTP/2 或 HTTP/3 且最大内容绘制 (LCP) 元素为图像的页面。所有查询和结果都是公开的。

注意:HTTP Archive 数据是使用 Chrome 的私有 WebPageTest 实例收集的。您可以详细了解他们的方法。

我假设 fetchpriority 的好处是发现资源的时间和开始下载的时间之间的时间差。我将此称为 “opportunity”。因此,如果一个资源发现得早,但浏览器开始下载的时间晚,那么 opportunity 就更大。

请注意,如果图像是从不同的域提供的,则会在 “opportunity” 中包括打开连接的时间。

上图绘制了针对 LCP 的 opportunity(以毫秒为单位)。opportunity 以 100 毫秒为一组进行存储,而大于 1,000 毫秒的任何时间都被分组到一个存储桶中。该图表显示了 “opportunity” 与 LCP 之间的强相关性 - opportunity 越大,LCP 越差。

上图显示了针对 Low 和 High 优先级的移动设备的 opportunity 分布。在中间值处,High 优先级请求的 LCP 图像在被发现后 21 毫秒开始下载,而 Low 优先级请求的 LCP 图像在 102 毫秒后开始下载。在 75% 和 90% 处差异甚至更大。

除了 fetchpriority="High" 之外,如果图像是较晚发现的,例如在使用 CSS background-image 或使用 JavaScript 添加图像时,图像可能具有初始的 High 优先级。在这些情况下,fetchpriority 将无济于事,因为请求已经具有 High 优先级。

我们可以得出结论,优先考虑 LCP 图像具有明显的好处。opportunity 因页面的组成而异。我们已经介绍过,当至少有一个渲染阻塞脚本和两个或多个正在进行的请求时,不会立即获取 Low 优先级资源。

上图绘制了渲染阻塞资源的数量与 opportunity(以毫秒为单位)。直观地说,您的页面拥有的渲染阻塞资源越多,下载 LCP 图像的延迟就越大。

结论

有很大的机会可以通过资源提示和获取优先级来确定 LCP 图像的优先级。即使 LCP 元素在主文档中立即可发现,许多页面仍会将其排队等待处理。

上图显示,在中等移动网站上,LCP 图像排队等待 98 毫秒,直到浏览器开始下载它。在 90% 处 LCP 图像排队等待 810 毫秒。使用 Fetch Priority 可以提高 LCP 图像的优先级并减少等待时间。

还有一些案例研究表明,在将 fetchpriority="high" 添加到 LCP 图像后,Largest Contentful Paint (LCP) 得到了改进。Etsy 的改进幅度为 4%,据报道其他一些改进幅度为 20-30%。

提高资源的优先级通常会以另一个资源为代价,因此应该谨慎使用获取优先级(Fetch Priority)。然而,如果浏览器正在将您的 LCP 图像排队等待处理,我建议您尝试使用 Fetch Priority,看看是否能减少等待时间并改善 LCP。

【第2952期】Google I/O 2023:提升 Web 核心性能指标优化建议

简而言之,

  • 将 LCP 图像托管在与 HTML 文档相同的域中。如果这不可行,请使用 preconnect 来建立早期连接。

  • LCP 图像应该是文档标记的一部分。如果这不可行,请使用 preload 告诉浏览器在请求之前下载图像。

  • 尽可能避免阻塞资源。如果您的 LCP 图像以 Low 优先级下载,请使用 fetchpriority 提示浏览器更早下载您的图像。

  • 在支持 fetchpriority 之前,您可以使用 preload 在 Firefox 上设置 LCP 图像的优先级。使用 preload 指令时,Safari 不会提前下载图像。

相关链接

  • Demos:https://fetchpriority-opportunity.glitch.me

  • Queries:https://github.com/kevinfarrugia/fetchpriority-opportunity

  • Results:https://docs.google.com/spreadsheets/d/11QwVrr1zcFGBhOMNe25uFBD9VUfg4JP3AUkhWjod9RY/edit#gid=0

  • Optimizing resource loading with Priority Hints(web.dev):https://web.dev/fetch-priority

  • Prioritizing Important Page Resources With Priority Hints(debugbear.com):https://www.debugbear.com/blog/priority-hints

备注

此功能最初称为 Priority Hints,但在标准化后更名为 Fetch Priority。

关于本文
译者:@吴彦祖火星分祖
译文:https://juejin.cn/post/7235959825544773689
作者:@Kevin
原文:https://imkev.dev/fetchpriority-opportunity

这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下 。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存