图像加载策略设计 - 掌握 preload、fetchpriority 和 decoding
图像加载的性能瓶颈与优化方向
图像加载涉及发现、请求、下载、解码四个阶段。每个阶段都有优化空间。
加载阶段:
- 发现:浏览器解析 HTML 发现 img 标签。预加载扫描器可提前发现
- 请求:发起 HTTP 请求。受并发连接数和优先级影响
- 下载:传输图像数据。受带宽和文件大小影响
- 解码:将压缩数据解码为位图。CPU 密集,可能阻塞主线程
LCP 图像的关键路径:LCP 元素通常是主视觉图。优化其加载路径可直接改善 LCP 指标。目标:让 LCP 图像尽早被发现、高优先级请求、快速下载、异步解码。
Chrome 的 5 级资源优先级:Chrome 内部以 Highest、High、Medium、Low、Lowest 5 个等级管理资源优先级。默认情况下视口内的图像被归为 High、视口外的图像被归为 Low;但这一判定要等布局计算完成后才进行,因此在初期阶段全部图像都被当作 Medium 处理,最优的优先级排布被推迟。另外 <picture> 元素内的图像要在媒体查询求值之后才能确定下载目标,被发现的时机比单纯的 <img> 更晚。
开发者可用的 3 个属性:针对上述问题,开发者可显式指示优先级的手段是 preload、fetchpriority、decoding 3 个属性。它们分别在不同层面优化图像加载,组合使用时存在把 LCP 改善 500ms-1 秒以上的案例。
preload - 提前发现关键图像
<link rel="preload"> 告诉浏览器尽早开始加载指定资源,即使 HTML 解析器尚未发现该资源。
用法:<link rel="preload" as="image" href="hero.webp">
适用场景:
- CSS 背景图:浏览器需要下载并解析 CSS 后才能发现背景图。preload 可提前数百毫秒开始加载
- JavaScript 动态插入的图像:JS 执行后才创建的 img 元素,preload 可提前加载
- LCP 图像:确保 LCP 图像以最高优先级尽早加载
响应式 preload:
<link rel="preload" as="image" imagesrcset="small.jpg 400w, large.jpg 800w" imagesizes="100vw">- 浏览器根据视口选择合适的尺寸预加载
注意:过度使用 preload 会与其他资源竞争带宽。仅对 LCP 图像和关键 CSS 背景图使用。
preload 特别有效的 3 种场合:把 <link rel="preload"> 写在 HTML 的 <head> 中,可让资源在 DOM 解析的最初期就开始取得。其一是 CSS 的 background-image 指定的图像——通常要等「CSS 文件下载 → 解析 → 构建 CSSOM → 构建渲染树」这一连串过程完成后,浏览器才认识到该图像的存在。其二是 SPA (Single Page Application) 的主视觉图,或轮播的第一张图像。其三是用 <picture> 做条件分支的图像。
实现例与滥用的警告:基本写法为 <link rel="preload" as="image" href="/images/hero.avif" type="image/avif" fetchpriority="high">;响应式图像的 preload 要用 imagesrcset 与 imagesizes 属性,写作 <link rel="preload" as="image" imagesrcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w" imagesizes="100vw" type="image/avif">。preload 很强力,但滥用会反效果:若 Chrome DevTools 的 Console 出现「preload 的资源在 3 秒内未被使用」的警告,就应删除不必要的 preload。
fetchpriority - 精细控制加载优先级
fetchpriority 属性让开发者明确告诉浏览器资源的重要程度,影响请求的调度顺序。
取值:
- high:高优先级。用于 LCP 图像、首屏关键图像
- low:低优先级。用于首屏外的图像、装饰性图像
- auto:浏览器自行判断(默认)
用法:<img src="hero.jpg" fetchpriority="high">
与 loading 属性的配合:
- LCP 图像:
loading="eager" fetchpriority="high"(不懒加载 + 高优先级) - 首屏装饰图:
loading="eager" fetchpriority="low"(不懒加载但低优先级) - 首屏外图像:
loading="lazy"(懒加载,fetchpriority 无意义)
效果:在带宽有限时,high 优先级的图像会先于 low 优先级的图像开始下载。可将 LCP 改善 100-400ms。
取值与内部效果:fetchpriority 的取值为 high、low、auto (默认) 3 个。fetchpriority="high" 的作用是把 Chrome 的内部优先级从 Medium 提升到 High。
实现例与适用范围:写作 <img src="hero.jpg" fetchpriority="high" alt="主视觉图" width="1200" height="600">,同时指定 width 与 height 也能防止 CLS。fetchpriority 可用于 <img>、<link rel="preload">、<script>、<iframe>。
decoding 属性 - 控制图像解码时机
decoding 属性控制图像解码是否阻塞主线程渲染。
取值:
- async:异步解码,不阻塞页面渲染。图像可能在渲染后才显示
- sync:同步解码,确保图像在下一帧渲染前解码完成。可能阻塞渲染
- auto:浏览器自行决定(默认)
推荐策略:
- 大多数图像:
decoding="async"。避免大图解码阻塞页面渲染 - LCP 图像:不设置或
decoding="auto"。让浏览器优化 LCP 的解码时机 - 动态插入的图像:
decoding="async"。避免 JavaScript 插入图像时阻塞主线程
与 createImageBitmap 的关系:对于需要在 Canvas 中使用的图像,createImageBitmap 可在 Web Worker 中解码,完全不阻塞主线程。
取值与效果的界限:decoding 的取值为 sync、async、auto (默认) 3 个。指定 async 时,图像解码期间 DOM 的构建与脚本的执行仍可继续,整页渲染因而加快;对 2,000 px 以上的大图,或多张图像同时解码的场面,效果尤为明显。反之,阻塞主线程会恶化 INP (Interaction to Next Paint),因此 sync 要谨慎使用。
综合加载策略设计
将各种加载优化技术组合为完整的策略,针对不同类型的图像应用不同的加载方式。
分层策略:
- LCP 图像(1 张):preload + fetchpriority="high" + 不懒加载 + 响应式 srcset
- 首屏其他图像(2-5 张):不懒加载 + fetchpriority="auto" + srcset
- 首屏外图像:loading="lazy" + 占位(aspect-ratio 或 LQIP)
- 装饰性图像:loading="lazy" + fetchpriority="low"
HTTP 头优化:
103 Early Hints:在 HTML 响应前提示浏览器预加载 LCP 图像Link: <hero.webp>; rel=preload; as=image:HTTP 头中的 preload
4 种典型模式:模式 1 (CSS 背景图的 LCP 优化)——在 <head> 写入 <link rel="preload" as="image" href="hero-bg.avif" type="image/avif" fetchpriority="high">,并对相应元素设置 background-image。模式 2 (首屏的 img 元素)——写作 <img src="hero.jpg" fetchpriority="high" decoding="async" loading="eager" width="1200" height="600" alt="...">,用 fetchpriority 提升优先级、用 decoding="async" 防止阻塞主线程、用 loading="eager" (默认) 关闭懒加载。模式 3 (视口外的图像)——写作 <img src="below-fold.jpg" loading="lazy" fetchpriority="low" decoding="async" width="800" height="400" alt="...">,把加载推迟到进入视口,并以 low 避免与其他图像争抢带宽。模式 4 (轮播的第一张图像)——只对第一张幻灯片设 fetchpriority="high" + loading="eager",第 2 张以后设 fetchpriority="low" + loading="lazy"。
实测效果:组合运用上述模式后,某电商网站的商品列表页 LCP 从 3.2 秒改善到 1.8 秒 (缩短 44%)。
性能监控与验证
实施加载策略后需要验证效果,持续监控性能指标。
验证工具:
- Chrome DevTools Network:查看图像的请求时序、优先级(Priority 列)和大小
- Lighthouse:检测未优化的图像(未压缩、未正确尺寸、缺少懒加载)
- WebPageTest:瀑布图可视化加载顺序,确认 LCP 图像是否最先加载
关键指标:
- LCP:最大内容绘制时间。LCP 图像的加载策略直接影响此指标
- CLS:累积布局偏移。懒加载图像的占位是否正确
- Total Blocking Time:图像解码是否阻塞了主线程
RUM(真实用户监控):
- 使用 PerformanceObserver 监控实际用户的 LCP 元素和时间
- 按设备类型、网络条件分析图像加载性能
- 识别特定图像导致的性能问题(如过大的未优化图像)
常见错误 3 例:错误 1 — 对 LCP 图像使用 loading="lazy":它会把下载推迟到图像接近视口,用在 LCP 图像上会造成 300-500ms 的延迟;应在 Chrome DevTools 的 Performance 面板中确定 LCP 元素,确认该图像没有加上 lazy。错误 2 — 过度使用 preload:preload 3 张以上的图像会引起带宽争抢,反而拖慢整体;要确认 Console 是否出现 The resource was preloaded using link preload but not used within a few seconds 的警告。错误 3 — 滥用 fetchpriority="high":对全部图像都设 high,优先级的区分就失去了意义。
验证与计测工具:在 Waterfall 图表中可视地确认各图像开始与完成下载的时刻,验证 LCP 图像是否最先下载完成;Lighthouse 的「Largest Contentful Paint element」一节可确认 LCP 元素与改善建议;WebPageTest 的 filmstrip view 能以 100ms 为单位追踪实际的渲染过程。RUM 方面用 web-vitals 库收集数据,比较引入 fetchpriority 前后的 LCP 分布——若 p75 值改善 200ms 以上,即可判定该措施成功。