配了三年图片加载,每次 LCP 都在最不该的地方卡住——今天我把 preload + fetchpriority 这套组合拳的账全算清楚了
配了三年图片性能优化,每次看到 LCP 分数垫底,第一反应就是去压图片体积、换 WebP、加 CDN。但折腾完发现——图片还是慢。根子根本不在文件大小,在于浏览器根本不知道哪张图最重要,你没告诉它先加载哪张。
今天把这件事彻底拆清楚——Chrome 这套 preload + fetchpriority 组合,把图片加载顺序从”浏览器自己猜”变成了”你说了算”。
问题:为什么图片体积优化完了 LCP 还是慢
LCP(Largest Contentful Paint)衡量的是页面最大内容元素何时可见。对于大多数网站,这个最大元素就是首屏那张图。
你可能已经做过这些事情:
- 把 PNG 换成 WebP/AVIF,体积压缩 40%
- 上了 CDN,就近分发
- 图片懒加载,非首屏图片 defer
但 LCP 分数还是绿不起来。
这不是压缩策略的问题,是加载顺序的问题。
浏览器在解析 HTML 时并不知道”哪张图对 LCP 影响最大”。它按照发现顺序加载资源——HTML 里先出现的先加载,后出现的排队等。如果你的 LCP 图片前面还挂着字体文件、统计脚本、广告脚本,那图片实际开始下载的时间比你以为的晚了几百毫秒甚至上秒。
这几百毫秒,就是 LCP 拉胯的根子。
解法一:<link rel="preload"> 告诉浏览器”这张图你先下”
preload 是 W3C 标准的资源提示指令,作用是”提前告诉浏览器这张图我很需要,你解析 HTML 的时候就开始下载,别等看到 <img> 标签再动”。
<!-- 在 <head> 最前面预加载 LCP 图片 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
as="image" 告诉浏览器这是一个图片资源,浏览器可以提前建立连接并开始下载。
fetchpriority="high" 进一步告诉浏览器:这张图的优先级是高的,在所有资源里排在前面。
两者一起用,浏览器在解析 HTML 阶段就开始下载 LCP 图片,不会被其他资源阻塞。
注意:preload 必须写在 <head> 里,在渲染阻塞资源之前。如果写在 <body> 后面,等于没说。
解法二:fetchpriority 直接标注重要程度
fetchpriority 是 HTML img 和 link 元素的属性(也叫 Priority Hints),它告诉浏览器对这张图用什么优先级。
<!-- 告诉浏览器这张是最高优先级 -->
<img src="/hero.webp" alt="Hero" fetchpriority="high" loading="eager">
<!-- 告诉浏览器这张不重要,可以往后排 -->
<img src="/decoration.webp" alt="" fetchpriority="low">
<!-- 非首屏图片,告诉浏览器"等页面主体加载完再说" -->
<img src="/below-fold.webp" alt="" loading="lazy">
fetchpriority 的三个值:
high:这张图对用户体验至关重要(首屏 Hero 图)low:这张图重要但不紧急(装饰性图片)auto:浏览器自己决定(默认行为)
注意:不要对首屏图片使用 loading="lazy",这会把下载时间推迟到页面渲染完之后,直接拉低 LCP。
实测数据:这两招能让 LCP 提升多少
Etsy 在生产环境实测,加上 fetchpriority="high" 后,LCP 时间降低了 4%。
听起来不多?但这是纯前端改动、不改后端、不换 CDN、不压图片,拿到的收益。
另一个站点的实验环境测试数据显示:LCP 提升 20%~30%。差异来自原本的加载顺序有多糟糕——如果 LCP 图片前面排着 5 个阻塞资源,优化效果就非常明显。
完整方案:三个配置一起用
生产环境推荐这样配置 LCP 图片:
<head>
<!-- 1. preload 提前告知浏览器这张图很重要 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<!-- 2. 关键 CSS 也 preload -->
<link rel="preload" href="/critical.css" as="style">
<!-- 3. LCP 图片本体明确标注 high + eager -->
</head>
<body>
<img
src="/hero.webp"
alt="Hero"
width="1200"
height="600"
fetchpriority="high"
loading="eager"
>
</body>
这三条配置分别解决了三个问题:
preload:让浏览器在解析 HTML 时就开始下载,不等看到<img>标签fetchpriority="high":在所有并发请求中,把这张图排在最前面loading="eager":确保首屏图片不会被懒加载推迟
常见的四个错误
错误一:给首屏图加 loading="lazy"
这是最常见的 LCP 杀手。懒加载会把图片下载推迟到页面渲染完之后,如果首屏图片被懒加载,LCP 直接崩。
<!-- ❌ 错误:首屏图不应该懒加载 -->
<img src="/hero.webp" loading="lazy">
<!-- ✅ 正确:首屏图 eager 或不加 -->
<img src="/hero.webp" loading="eager">
错误二:preload 和 img 同时出现但属性不一致
如果 preload 指定了 fetchpriority="high",但 img 标签里忘了写,浏览器可能会以不同优先级下载两次或产生混乱。
<!-- ✅ 两边保持一致 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<img src="/hero.webp" fetchpriority="high">
错误三:preload 写在 body 里
preload 必须放在 <head> 里,而且尽量靠前。如果写在某个 JS 脚本后面,脚本本身会阻塞 HTML 解析,preload 的提前通知效果就浪费了。
错误四:对所有图片都用 high
fetchpriority="high" 的作用是提高某张图的优先级,如果所有图片都是 high,就等于没有优先级。high 留给唯一的 LCP 图片,其他图默认 auto 或 low。
下一步:怎么验证生效了
用 Chrome DevTools Performance 面板录制一次页面加载,看 Network 面板里 LCP 图片的 Start Time。
配置前:图片在所有阻塞资源完成后才开始下载
配置后:图片和 HTML 解析同步开始,优先级最高
也可以用 Performance Observer 监听 LCP 元素:
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP element:', lastEntry.element);
console.log('LCP time:', lastEntry.startTime);
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
配合 Resource Timing API 还能看到这张图的 DNS 连接时间、TTFB、下载时间:
const entries = performance.getEntriesByType('resource');
const lcpImage = entries.find(e => e.name.includes('hero.webp'));
console.log('DNS:', lcpImage.domainLookupEnd - lcpImage.domainLookupStart);
console.log('TTFB:', lcpImage.responseStart - lcpImage.requestStart);
console.log('Download:', lcpImage.responseEnd - lcpImage.responseStart);
配图片性能优化配了三年,每次 LCP 分数一绿就觉得是图片太重了。但真正的瓶颈从来不是体积——是顺序。preload 提前喊”这张图你先下”,fetchpriority 告诉浏览器”这张图最重要”,两条配置加上去,LCP 分数的变化会让你重新评估之前的优化方向。
评论区
登录后可评论。