大家好,我是 ConardLi。
对于一个网页的性能和体验来讲,控制好请求发起的优先级是非常重要的,网络带宽是有限的,优先去加载重要的资源,让次要的资源延后,就可以让我们的网站体验提升一个台阶。
浏览器本身非常擅长确定网页资源请求的优先级,而且大多数情况下也做的挺好的。但我们肯定还是会遇到一些特殊的优先级控制需求,Priority Hints
可以轻松的帮助我们主动控制网站请求加载的优先级。
我在两年前写过一篇优先级相关的文章:如何控制Web资源加载的优先级?
当时的 API 只是 Priority Hints
的雏形,如今使用方式有了比较大的变化,我们再来一起学习下。
预加载资源的优先级
现代浏览器有一种受到光房支持的方式来提前获取当前页面最终需要的资源:<link rel="preload" ... />
。当我们将它放置在 HTML 的 <head>
中时,浏览器将被指示以 “高” 优先级尽快开始下载它。
浏览器中的预加载扫描器已经非常擅长此类事情了,所以,预加载通常最适合用于后来发现的资源 - 任何未由 HTML
直接加载的资源,例如通过内联样式属性加载的背景图像。但它对于浏览器可能没有按照你想要的优先顺序排列的任何其他内容也很有用。
举个例子:默认情况下,Chrome
会加载具有非常高优先级的字体,但如果某个用户的网络连接速度较慢,它会使用后备字体并降低优先级。
我们通过 CSS @font-face
规则加载的字体:
@font-face {
font-family: "Inter Variable";
src: url("./font.woff2") format("woff2");
}
加载时,由于网速较慢,这个字体的下载优先级最低,尽管它对于页面的视觉体验非常重要。
但我们可以通过预加载这个资源来覆盖浏览器的决定:
<head>
<!-- Other stuff... -->
<link rel="preload" href="/font.woff2" as="font">
</head>
现在它的优先级提高了:
另外,我们可以在 link
上使用 fetchpriority
,来让我们在一次预加载多个资源时发出更精准的优先级信号。
<link rel="preload" href="./font-1.woff2" as="font" fetchpriority="low" />
<link rel="preload" href="./font-2.woff2" as="font" fetchpriority="high" />
浏览器会根据我们的指令自动推断:
fetch 请求的优先级
在我看来,Fetch API
是现代网络中最符合人体工程学的设计之一。它具有 XMLHttpRequest
所缺乏的一些很好的功能,例如在发出请求上控制优先级信号的能力。
当带宽有限并且同时存在多个请求时,浏览器会做出自己的优先级决定。
如果我们同时发送两个请求(一个日志上报的请求,一个重要的支付请求),这两个请求几乎会同时进入排队:
fetch("http://localhost:8000/pay", { method: "POST", body: paymentBody, });
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
});
默认情况下,浏览器会赋予它们默认的高优先级。
现在,我们明确告诉浏览器每个请求应如何确定优先级:
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
priority: "high"
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
priority: "low"
});
然后浏览器的判定就不一样了:
img 资源的优先级
在默认情况下,无需我们做任何特殊的事情,浏览器就会自动确定页面上最重要的图像。
比如我们来看下面的例子,有三个垂直排列的图片,并且距离非常远:
<img src="./cat-1.jpeg" />
<div style="height: 1700px"></div>
<img src="./cat-2.jpeg" />
<div style="height: 1700px"></div>
<img src="./cat-3.jpeg" />
当刚开始下载时,所有三个都是 “Low” 优先级:
但不久之后,首屏就变成了 “High”。
浏览器自己发现了哪个是最重要的,但花了大约一秒钟。
当我们向第一张图像添加 fetchpriority
属性时,浏览器就不需要再花额外的时间来预测了:
<img src="./cat-1.jpeg" fetchpriority="high" />
之后,cat-1.jpeg
就会立即以最高优先级加载。
浏览器虽然可以智能地确定资源的重要性,但如果有更清晰的指令,它的反应时间会更快。
所以,如果我们知道哪张图像是很重要的,可以明确说明。
顺便说一下,这个功能与图像的懒加载机制非常匹配,目前得到了很好支持。
<img src="./cat-1.jpeg" fetchpriority="high"/>
<div style="height: 1700px"></div>
<img src="./cat-2.jpeg" loading="lazy" />
<div style="height: 1700px"></div>
<img src="./cat-3.jpeg" loading="lazy" />
有了这个功能,浏览器就能准确地知道如何(以及是否)加载图片,而且只在适当的时候加载。在上面的例子中,浏览器甚至不会在首屏就请求屏幕外的图像,而是会等到它们更靠近视口时才开始。
script 脚本的优先级
页面上任何带有 src
属性的普通 <script />
都会拥有比较高的优先级,但有一个需要权衡的事情:它们会阻止对页面其余部分的解析,直到加载并执行完成为止。
因此,我们可能会使用到 async
属性,它会在后台以低优先级请求脚本,并在准备就绪后立即执行。我们来看下面的例子:
<script src="/script-async.js" async onload="console.log('async')"></script>
<script src="/script-sync.js" onload="console.log('sync')"></script>
<script>console.log("inline");</script>
async
脚本会降低优先级:
控制台确认,在异步脚本加载的过程中,允许后续脚本解析和执行。
但是,如果我们又希望脚本异步下载,也希望脚本拥有 “High” 优先级呢。
一个可能的场景是将一个小型 SPA
嵌入到一个落地页的主要内容中。为了保持页面的 Core Web Vitals
,特别是 LCP
(最大内容绘制时间)和 FID
(首次输入延迟),你需要将该脚本设置为高优先级(毕竟,它负责构建和驱动你的应用程序)。但与此同时,你不希望它阻碍页面的其余部分进行解析。
所以,让我们给它一个 fetchpriority
:
<script src="/script-async.js" async onload="console.log('async')" fetchpriority="high"></script>
<script src="/script-sync.js" onload="console.log('sync')"></script>
<script>console.log("inline");</script>
现在,它以更高的优先级下载,同时仍然不会阻止页面的其余部分:
优先级越高,异步脚本加载得越快,在这种情况下,甚至可能在同步和内联之前。
同样的思路,fetchpriority
也可以用来延迟脚本。
当我们预先知道脚本的优先级,并且浏览器可能也没有足够的信息来自行确定时,可以将
fetchpriority
添加到脚本上。它对于还想以非阻塞、异步方式加载的脚本的优先级特别有帮助。
最后
当然,fetchpriority
也千万不要过度使用,因为加的多了就等于什么都没加,甚至会导致网页的性能下降。
把 fetchpriority
添加到那些浏览器推断的不好,并且你有明确优先级要求的元素上,对网页的加载体验还是会很大的!
参考:
- https://macarthur.me/posts/priority-hints
- https://developer.mozilla.org/en-US/docs/Web/API/HTMLLinkElement/fetchPriority?ref=alex-macarthur
- https://calendar.perfplanet.com/2022/http-3-prioritization-demystified/?ref=alex-macarthur
- https://github.com/WICG/priority-hints/blob/main/EXPLAINER.md?ref=alex-macarthur
- https://developers.google.com/search/blog/2023/05/introducing-inp?ref=alex-macarthur
如果你想加入高质量前端交流群,或者你有任何其他事情想和我交流也可以添加我的个人微信 ConardLi 。
点赞
和在看
是最大的支持⬇️❤️⬇️