使用 Fetch Priority API 优化资源加载

Fetch Priority API 用于指明资源相对于浏览器的相对优先级。它可以实现最佳加载效果并改进核心 Web 指标。

Leena Sohoni
Leena Sohoni
Patrick Meenan
Patrick Meenan

浏览器支持

  • Chrome:102.
  • Edge:102.
  • Firefox:132.
  • Safari:17.2。

来源

当浏览器解析网页并开始发现和下载图片、脚本或 CSS 等资源时,会为这些资源分配一个提取 priority,以便以最佳顺序下载它们。资源的优先级通常取决于其类型以及在文档中的位置。例如,视口内的图片可能具有 High 优先级,而使用 <head> 中的 <link> 的提前加载的渲染阻塞 CSS 的优先级可能是 Very High。浏览器非常擅长分配效果良好的优先级,但在所有情况下都可能不是最佳优先级。

本页介绍了 Fetch Priority API 和 fetchpriority HTML 属性,可让您提示资源的相对优先级(highlow)。Fetch Priority 有助于优化 Core Web Vitals。

摘要

提取优先级可在以下几个关键领域提供帮助

  • 通过在图片元素上指定 fetchpriority="high",提高 LCP 图片的优先级,从而更快发生 LCP。
  • 提高 async 脚本的优先级,使用的语义比当前最常见的黑客攻击(为 async 脚本插入 <link rel="preload">)更好。
  • 降低了正文末尾脚本的优先级,以便更好地与图片进行排序。
幻灯片视图,比较 Google 机票首页的两项测试。在底部,提取优先级用于提升主推图片的优先级,从而使 LCP 缩短了 0.7 秒。
在 Google 机票测试中,抓取优先级将 Largest Contentful Paint 从 2.6 秒缩短到了 1.9 秒。

过去,开发者使用 preloadpreconnect 对资源优先级的影响有限。您可以通过预加载告知浏览器您想要在浏览器自然发现这些资源之前提前加载的关键资源。这对于更难发现的资源特别有用,例如样式表中包含的字体、背景图片或从脚本加载的资源。预连接有助于预热到跨源服务器的连接,并有助于提升首次收到字节所用时间等指标。如果您知道来源,但不一定知道所需资源的确切网址,则此方法非常有用。

提取优先级是对这些资源提示的补充。它是一种基于标记的标志,可通过 fetchpriority 属性提供,开发者可以使用它来指明特定资源的相对优先级。您还可以通过 JavaScript 和 Fetch API 使用 priority 属性来使用这些提示,以影响为数据提取资源的优先级。提取优先级还可以与预加载相辅相成。取一个 LCP 图片,即使预加载,其优先级仍会较低。如果它被其他早期低优先级资源推迟,使用提取优先级有助于加快图片加载速度。

资源优先级

资源下载顺序取决于浏览器为网页上的每个资源分配的优先级。可能影响优先级计算逻辑的因素包括:

  • 资源类型,例如 CSS、字体、脚本、图片和第三方资源。
  • 文档引用资源的位置或顺序。
  • 脚本是否使用了 asyncdefer 属性。

下表显示了 Chrome 如何对大多数资源进行排优先级和排序:

  在布局阻塞阶段加载 在布局阻塞阶段逐个加载
Blink
优先级
VeryHigh 中等 VeryLow
开发者工具
优先级
最高 中等 最低
主要资源
CSS(早期**) CSS(**晚些时候) CSS(媒体不匹配***)
脚本(提前** 或非从预加载扫描器加载) 剧本(**晚**) 脚本(异步)
字体 字体(rel=preload)
导入
图片(在视口中) 图片(前 5 张图片 > 10,000 像素 2) Image
媒体(视频/音频)
预取
XSL
XHR(同步) XHR/fetch*(异步)

浏览器会按照发现资源的顺序下载具有相同计算优先级的资源。您可以在 Chrome 开发者工具的 Network(网络)标签页下查看为加载网页时分配给不同资源的优先级。(请务必右键点击表格标题并选中优先级列)。

Chrome 开发者工具的“Network”(网络)标签页,其中列出了多个字体资源。它们都是最高优先级。
BBC 新闻详情页面上资源 type = "font" 的优先级
Chrome 开发者工具的“Network”(网络)标签页,其中列出了多个字体资源。它们混合了低优先级和高优先级。
BBC 新闻详情页面上资源 type = "script" 的优先级

优先级发生变化时,您可以在大型请求行设置或提示中同时查看初始优先级和最终优先级。

Chrome 开发者工具的“Network”(网络)标签页。“大请求行”设置已选中,并且“优先级”列显示第一个图片的优先级为“高”,下面的另一个图片的初始优先级为“中”。提示中也会显示相同的信息。
DevTools 中的优先级变更。

何时可能需要提取优先级?

现在,您已经了解了浏览器的优先级逻辑,可以调整网页的下载顺序,以优化其性能和核心网页指标。以下是一些可更改的设置示例,可影响资源下载的优先级:

  • 按照您希望浏览器下载资源的顺序放置资源标记(例如 <script><link>)。具有相同优先级的资源通常会按发现的顺序加载。
  • 使用 preload 资源提示尽早下载必要的资源,尤其是浏览器不易在早期发现的资源。
  • 使用 asyncdefer 下载脚本,而不会屏蔽其他资源。
  • 延迟加载首屏线以下的内容,以便浏览器可以将可用带宽用于更关键的首屏线以上资源。

这些方法有助于控制浏览器的优先级计算,从而提升性能和核心网页指标。例如,预加载关键背景图片后,系统可以更早发现该图片,从而缩短 Largest Contentful Paint (LCP) 时间。

有时,这些句柄可能不足以为您的应用最佳地确定资源优先级。以下是提取优先级可能有用的场景:

  • 您有多个首屏图片,但它们的优先级不应完全相同。例如,在图片轮播界面中,只有第一个可见图片需要更高的优先级,而其他图片(通常最初位于屏幕外)可以设置为较低的优先级。
  • 视口内的图片通常以 Low 优先级开始。布局完成后,Chrome 会发现它们位于视口内,并提高其优先级。这通常会导致加载关键图片(例如主打图片)出现明显延迟。在标记中提供提取优先级可让图片以 High 优先级启动,并提前加载。为了尽量实现自动化,Chrome 会将前五张较大的图片设为 Medium 优先级,这会有所帮助,但使用显式 fetchpriority="high" 会更好。

    如需尽早发现作为 CSS 背景包含的 LCP 图片,仍需要预加载。如需提高背景图片的优先级,请在预加载中添加 fetchpriority='high'
  • 将脚本声明为 asyncdefer 会告知浏览器异步加载它们。不过,如优先级表所示,这些脚本也被分配了“低”优先级。您可能需要提高其优先级,同时确保异步下载,尤其是对于对用户体验至关重要的脚本。
  • 如果您使用 JavaScript fetch() API 异步提取资源或数据,浏览器会为其分配 High 优先级。您可能希望让某些提取操作以较低的优先级运行,尤其是在将后台 API 调用与响应用户输入的 API 调用混合使用时。将后台 API 调用标记为 Low 优先级,将 Interactive API 调用标记为 High 优先级。
  • 浏览器会为 CSS 和字体分配 High 优先级,但其中一些资源可能比其他资源更重要。您可以使用提取优先级来降低非关键资源的优先级(请注意,早期 CSS 会阻塞渲染,因此通常应设为 High 优先级)。

fetchpriority 属性

使用 fetchpriority HTML 属性可指定使用 linkimgscript 标记下载的 CSS、字体、脚本和图片等资源类型的下载优先级。它可以采用以下值:

  • high:资源的优先级更高,并且您希望浏览器将其优先级设得高于平常,前提是浏览器自己的启发词语不会阻止这种情况。
  • low:资源的优先级较低,并且您希望浏览器降低其优先级(如果其启发词语允许的话)。
  • auto:默认值,可让浏览器选择适当的优先级。

以下是一些在标记中使用 fetchpriority 属性以及等效于脚本的 priority 属性的示例。

<!-- We don't want a high priority for this above-the-fold image -->
<img src="/images/in_viewport_but_not_important.svg" fetchpriority="low" alt="I'm an unimportant image!">

<!-- We want to initiate an early fetch for a resource, but also deprioritize it -->
<link rel="preload" href="/js/script.js" as="script" fetchpriority="low">

<script>
  fetch('https://example.com/', {priority: 'low'})
  .then(data => {
    // Trigger a low priority fetch
  });
</script>

浏览器优先级和 fetchpriority 的影响

您可以将 fetchpriority 属性应用于不同的资源(如下表所示),以提高或降低其计算出的优先级。每行中的 fetchpriority="auto" (◉) 标记了相应资源类型的默认优先级。(也可以 Google 文档的形式提供)。

  在布局阻塞阶段加载 在布局阻塞阶段一次加载一个
Blink
优先级
VeryHigh 中等 VeryLow
开发者工具
优先级
最高 中等 最低
主要资源
CSS(早期**) ⬆◉
CSS(**晚些时候)
CSS(媒体不匹配***) ⬆*** ◉⬇
脚本(提前** 或非从预加载扫描器加载) ⬆◉
剧本(**晚**)
脚本(异步/延迟) ◉⬇
字体
字体(rel=preload) ⬆◉
导入
图片(在视口中 - 布局后) ⬆◉
图片(前 5 张图片 > 10,000 像素 2)
Image ◉⬇
媒体(视频/音频)
XHR(同步)- 已废弃
XHR/fetch*(异步) ⬆◉
预取
XSL

fetchpriority 会设置相对优先级,这意味着它会将默认优先级提高或降低适当的量,而不是将优先级明确设置为 HighLow。这通常会导致 HighLow 优先级,但并不总是如此。例如,使用 fetchpriority="high" 的关键 CSS 会保留“非常高”/“最高”优先级,而对这些元素使用 fetchpriority="low" 会保留“高”优先级。这两种情况都不会涉及将优先级明确设置为 HighLow

使用场景

如果您想向浏览器提供有关以何种优先级提取资源的额外提示,请使用 fetchpriority 属性。

提高 LCP 图片的优先级

您可以指定 fetchpriority="high" 来提高 LCP 或其他关键图片的优先级。

<img src="lcp-image.jpg" fetchpriority="high">

以下对比图展示了启用和未启用提取优先级时,包含 LCP 背景图片的 Google 机票页面。将优先级设为高后,LCP 从 2.6 秒缩短到了 1.9 秒

一项实验,使用 Cloudflare 工作器使用提取优先级重写 Google 航班页面。

使用 fetchpriority="low" 可降低非紧急的置顶图片的优先级,例如图片轮播界面中的屏幕外图片。

<ul class="carousel">
  <img src="img/carousel-1.jpg" fetchpriority="high">
  <img src="img/carousel-2.jpg" fetchpriority="low">
  <img src="img/carousel-3.jpg" fetchpriority="low">
  <img src="img/carousel-4.jpg" fetchpriority="low">
</ul>

虽然图片 2-4 会超出视口范围,但系统可能会认为它们“足够接近”,因此会将它们提升到 high,即使添加了 load=lazy 属性,它们也会加载。因此,fetchpriority="low" 是此问题的正确解决方案。

在之前针对 Oodle 应用进行的一项实验中,我们就使用了此方法来降低在加载时不会显示的图片的优先级。这缩短了网页加载时间 2 秒。

在 Oodle 应用的图片轮播界面中使用提取优先级的并排比较。在左侧示例中,浏览器为轮播界面图片设置了默认优先级,但下载和绘制这些图片的速度比右侧示例慢了约 2 秒,后者仅为第一张轮播界面图片设置了更高的优先级。
仅为第一个轮播界面图片使用高优先级可加快网页加载速度。

降低预加载资源的优先级

如需停止预加载的资源与其他关键资源争用,您可以降低其优先级。将此技术用于图片、脚本和 CSS。

<!-- Lower priority only for non-critical preloaded scripts -->
<link rel="preload" as="script" href="critical-script.js">
<link rel="preload" as="script" href="non-critical-script.js" fetchpriority="low">

<!-- Preload CSS without blocking render, or other resources -->
<link rel="preload" as="style" href="theme.css" fetchpriority="low" onload="this.rel='stylesheet'">

重新排定脚本的优先级

网页需要的脚本应快速加载,但不应阻塞其他更重要的呈现阻塞资源。您可以将这些任务标记为 async,并设置高优先级。

<script src="async_but_important.js" async fetchpriority="high"></script>

如果脚本依赖于特定 DOM 状态,则无法将其标记为 async。不过,如果这些脚本要在页面上稍后运行,您可以降低其加载优先级:

<script src="blocking_but_unimportant.js" fetchpriority="low"></script>

这样一来,当解析器到达此脚本时,它仍会被屏蔽,但会允许优先处理此脚本之前的内容。

如果需要完整的 DOM,则可以使用 defer 属性(在 DOMContentLoaded 之后按顺序运行),甚至可以使用网页底部的 async

降低非关键数据提取的优先级

浏览器会以高优先级执行 fetch。如果您有多个可能同时触发的提取操作,则可以为更重要的数据提取操作使用较高的默认优先级,并降低不太重要的数据的优先级。

// Important validation data (high by default)
let authenticate = await fetch('/user');

// Less important content data (suggested low)
let suggestedContent = await fetch('/content/suggested', {priority: 'low'});

提取优先级实现说明

提取优先级可以在特定用例中提升性能,但在使用提取优先级时需要注意以下几点:

  • fetchpriority 属性是一个提示,而非指令。浏览器会尝试遵循开发者的偏好设置,但也可以应用其资源优先级偏好设置来解决冲突。
  • 请勿将提取优先级与预加载混淆:

    • 预加载是一项强制提取操作,而非提示。
    • 通过预加载,浏览器可以提前发现资源,但仍会以默认优先级提取资源。反之,提取优先级对可检测性没有帮助,但可以让您提高或降低提取优先级。
    • 与优先级更改的影响相比,预加载的影响通常更容易观察和衡量。

    提取优先级可以通过提高优先级的粒度来补充预加载。如果您已将预加载指定为 LCP 图片的 <head> 中的第一个项,则 high 提取优先级可能不会显著缩短 LCP 用时。不过,如果预加载发生在其他资源加载之后,high 提取优先级可以进一步缩短 LCP。如果关键图片是 CSS 背景图片,请使用 fetchpriority = "high" 预加载该图片。

  • 在更多资源争夺可用网络带宽的环境中,优先级功能带来的加载时间缩短效果更为明显。对于无法并行下载的 HTTP/1.x 连接,或者带宽较低的 HTTP/2 或 HTTP/3 连接,这种情况很常见。在这些情况下,优先处理有助于解决瓶颈问题。

  • CDN 不会统一实现 HTTP/2 优先级,HTTP/3 也是如此。即使浏览器通过提取优先级传达优先级,CDN 也可能不会按指定顺序重新确定资源的优先级。这使得测试提取优先级变得困难。这些优先级会在浏览器内部以及支持优先级的协议(HTTP/2 和 HTTP/3)中应用。即使不考虑 CDN 或源站支持,仅出于内部浏览器优先级考虑,也值得使用提取优先级,因为浏览器请求资源时,优先级通常会发生变化。例如,在浏览器处理关键 <head> 项时,系统通常会阻止请求图片等低优先级资源。

  • 在初始设计中,您可能无法将提取优先级作为最佳实践引入。在开发周期的后期,您可以为网页上的不同资源分配优先级,如果这些优先级与您的预期不符,您可以引入提取优先级以进行进一步优化。

开发者应将预加载用于其预期用途,即预加载解析器未检测到的资源(字体、导入内容、背景 LCP 图片)。preload 提示的位置将影响资源的预加载时间。

提取优先级与提取资源时应采用的方式有关。

关于使用预加载的提示

使用预加载时,请注意以下事项:

  • 在 HTTP 标头中添加预加载项会将其放在加载顺序中的所有其他内容之前。
  • 通常,对于优先级为 Medium 或更高的任何内容,预加载项会按解析器获取它们的顺序加载。如果您要在 HTML 开头部分添加预加载内容,请务必小心。
  • 在头部末尾或正文开头预加载字体可能效果最好。
  • 导入预加载(动态 import()modulepreload)应在需要导入的脚本标记之后运行,因此请确保先加载或解析脚本,以便在其依赖项加载时对其进行评估。
  • 默认情况下,图片预加载的优先级为 LowMedium。将它们相对于异步脚本和其他低优先级或最低优先级的代码进行排序。

历史记录

我们于 2018 年首次在 Chrome 中以源试用形式对提取优先级进行了实验,并于 2021 年再次使用 importance 属性进行了实验。当时,它被称为优先级提示。此接口已在 Web 标准流程的一部分中更改为:HTML 的接口为 fetchpriority,JavaScript 的 Fetch API 的接口为 priority。为避免混淆,我们现在将此 API 提取优先级称为“API 提取优先级”。

总结

随着预加载行为的修复以及近期对 Core Web Vitals 和 LCP 的关注,开发者可能会对提取优先级感兴趣。现在,他们可以使用其他控件来实现首选的加载顺序。