<link rel="preload"> 是最简首屏关键图预加载方案,需写在 <head> 中、as="image" 不可省略、href 为静态绝对路径;动态场景用 new Image() 精确控制时机;loading="lazy" 等非预加载手段勿误用。

用 <link rel="preload"> 声明首屏关键图最省事
浏览器原生支持、不写 JS、不改逻辑,只要在 <head> 里加一行就生效。它只下载、不渲染、不占布局,也不触发重排,Lighthouse 能识别,CDN 和缓存策略也完全兼容。
- 必须写在
<head>中,且越靠前越好;动态插入(比如 JS 里document.createElement('link'))完全无效 -
as="image"是硬性要求,漏掉或写成as="fetch"会导致 Priority 降为 Low,实测延迟 200–400ms -
href必须是静态绝对路径,不能含 JS 变量、模板语法(如"/img/${name}.webp")或相对路径(如"./assets/hero.jpg"),否则 404 或路径错配 - Chrome 101+ 可加
fetchpriority="high"进一步抬高优先级,旧版忽略无副作用
<link rel="preload" as="image" href="/images/hero.webp" fetchpriority="high">
用 new Image() 控制运行时预加载时机
当预加载要依赖用户行为、设备特性或状态判断时(比如 hover 后加载下一页缩略图、按 window.devicePixelRatio 加载 @2x 版本),new Image() 是唯一能精确控制的方案。
- 顺序不能错:先绑定
onload/onerror,再赋值src;否则缓存命中时事件同步触发,回调直接跳过 - 检查
img.complete:为true说明已缓存,需立刻执行成功逻辑 - 避免密集创建:浏览器并发请求数通常只有 6~8 个,循环中
new Image()容易阻塞;建议加 Promise 队列,每次最多并发 4 个 - 不需要插入 DOM,赋值
src即触发下载
const preloadImage = (url) => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Failed to load ${url}`)); img.src = url; });};别把 loading="lazy" 或 display: none 当预加载用
这是最常被误用的点:loading="lazy" 是推迟加载,不是提前加载。哪怕你给首屏 <img> 显式加上它,Chrome 仍可能等到滚动前 500px 才发请求——和预加载目标完全相反。
-
loading="eager"只是“不懒”,不等于“提前”;它只是恢复默认行为,对加载时机无增强作用 -
decoding="async"只影响解码线程,和下载时机无关 -
display: none或 CSS background 伪隐藏(如background: url(...) -9999px -9999px)无法保证加载优先级,还会污染样式表、干扰渲染流程,且无法监听状态
WebP 预加载必须带 fallback,且不能和普通 <img src> 重复声明
<link rel="preload"> 不支持 srcset 或 sizes,只适合尺寸/格式确定的单图(如 banner、logo)。如果用了 WebP,就得自己兜底:
立即学习“前端免费学习笔记(深入)”;
- 预加载 WebP 的同时,主
<img>标签仍要用<picture>+<source type="image/webp">+<img src="fallback.jpg">结构 - 不要对同一个 URL 既写
<link rel="preload">又在<img src>里引用——路径大小写、查询参数(如?v=1)稍有差异,就会导致缓存不复用,反而多一次请求
真正需要预加载的,只有浏览器“发现太晚”的资源:比如内联 style 里的 background-image、<picture> 中的关键 <source>,或者 JS 动态拼接的图片路径。首屏已写死的 <img src>,直接加 fetchpriority="high" 就够了。



















