图片等组件运行以后才插入,浏览器就要等到那时才知道图片地址。想让它更早加载,可以在 HTML 里声明 preload;fetchpriority 则是对已经发现的资源给出优先级提示。
两者都和加载有关,但影响的步骤不同。用一张晚出现的图片,能把这个区别看得比较清楚。
图片延后出现,服务端延后返回
创建一个空目录,把下面存为 index.html:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>资源顺序</title>
<script>
const mode = new URLSearchParams(location.search).get("mode") || "baseline";
if (mode === "preload")
document.write('<link rel="preload" as="image" href="/asset.svg">');
</script>
</head>
<body>
<h1>资源发现时间实验</h1>
<p>mode=baseline / preload / priority;人为延迟插入图片,不代表真实站点。</p>
<div id="image"></div>
<pre id="result"></pre>
<script>
setTimeout(() => {
const image = new Image();
image.width = 800;
image.height = 400;
if (mode === "priority") image.fetchPriority = "high";
image.src = "/asset.svg";
image.onload = () => {
document.querySelector("#result").textContent = JSON.stringify(
performance
.getEntriesByName(new URL("/asset.svg", location.href).href)
.map((entry) => ({
start: entry.startTime,
duration: entry.duration,
bytes: entry.transferSize,
})),
null,
2,
);
};
document.querySelector("#image").append(image);
}, 300);
</script>
</body>
</html>
创建 server.mjs:
import http from "node:http";
import { readFile } from "node:fs/promises";
http
.createServer(async (req, res) => {
const url = new URL(req.url, "http://localhost");
if (url.pathname === "/asset.svg") {
setTimeout(() => {
if (res.destroyed) return;
res.writeHead(200, {
"Content-Type": "image/svg+xml",
"Cache-Control": "no-store",
});
res.end(
'<svg xmlns="http://www.w3.org/2000/svg" width="800" height="400"><rect width="800" height="400" fill="#d9e6e3"/></svg>',
);
}, 120);
return;
}
if (url.pathname !== "/") {
res.writeHead(404);
return res.end();
}
try {
res.writeHead(200, {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "no-store",
});
res.end(await readFile(new URL("index.html", import.meta.url)));
} catch {
res.end("缺少 index.html");
}
})
.listen(3000, "127.0.0.1");
运行 node server.mjs,依次访问 http://127.0.0.1:3000/?mode=baseline、?mode=preload、?mode=priority。
页面等待 300ms 才插入图片,服务端等待 120ms 返回。baseline 没有提示,preload 在 head 提前声明地址,priority 只在插入图片时设置 high。
记录请求什么时候开始
图片加载后,页面显示 Resource Timing 的 start、duration 和 bytes。start 相对于这次页面的时间原点,不是服务器的绝对时间。
在本地 HTTP、Edge 155.0.4283.45 下,每个条件使用三个独立浏览器上下文,服务端设置 no-store,记录过下面一组开始时间:
| 条件 | 三次 startTime |
|---|---|
| baseline | 322.7ms、317.7ms、321.2ms |
| preload | 12.4ms、12.3ms、13.0ms |
| priority | 321.9ms、314.6ms、316.0ms |
这组数据说明 preload 让浏览器更早发现地址。插入时才设置 high,仍然要等到插入以后才能请求。这里没有制造资源竞争,不能拿它评价 fetchpriority 在复杂页面里的调度收益,也没有测量 LCP。
手动重跑时,用新的浏览器上下文,或者关闭缓存并重新加载,每个条件重复几次。不要把三次不同缓存状态的访问直接拿来比较。
提前下载也占用带宽
preload 的 URL、as 和跨源凭据模式要与实际使用匹配,否则可能不能复用。提前加载的资源应该很快被使用,不然它可能和更关键的请求抢带宽。
prefetch 偏向未来可能用到的资源,preconnect 准备连接,dns-prefetch 处理域名解析。它们不是同一个开关。属性说明参考 MDN preload 文档 和 fetchpriority 文档。
首屏图片是否要提前加载,还是应该结合页面实际的发现时机和 LCP 看。并不是所有图片都设置 high,就能让页面更快。