[译]Fetch Priority 和优化LCP
原文发布时间:2023 年 1 月 2 日 原文更新时间:2023 年 5 月 2 日
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 结合起来。
<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。
简而言之,
- 将 LCP 图像托管在与 HTML 文档相同的域中。如果这不可行,请使用
preconnect
来建立早期连接。 - LCP 图像应该是文档标记的一部分。如果这不可行,请使用
preload
告诉浏览器在请求之前下载图像。 - 尽可能避免阻塞资源。如果您的 LCP 图像以
Low
优先级下载,请使用fetchpriority
提示浏览器更早下载您的图像。 - 在支持
fetchpriority
之前,您可以使用preload
在 Firefox 上设置 LCP 图像的优先级。使用preload
指令时,Safari 不会提前下载图像。
相关链接
Demos Queries & Results Optimizing resource loading with Priority Hints(web.dev) Prioritizing Important Page Resources With Priority Hints(debugbear.com)
备注
此功能最初称为 Priority Hints,但在标准化后更名为 Fetch Priority。
转载自:https://juejin.cn/post/7235959825544773689