图片预加载核心是后台异步下载并缓存资源,而非阻塞渲染等待完成;new image()需先绑定onload/onerror再赋src,配合img.complete检测缓存状态,并用promise封装确保加载结果可控,避免静默失败或回调覆盖。

图片预加载不是靠“等它加载完再用”,而是让浏览器在后台悄悄下载资源,等真正需要时直接从缓存取——关键在时机控制和加载状态判断,而不是强行阻塞渲染。
用 new Image() 触发预加载最简单但容易失效
这是最常被抄的写法,但很多人没意识到它只管发起请求,不保证加载完成或失败处理:
-
new Image()创建后立刻赋值src,浏览器就开始下载,但没回调机制 - 如果图片 404 或跨域受限,
onerror会触发,但默认静默失败,你根本不知道预加载没成功 - 多个图片串行写
img.onload = ...容易覆盖,必须为每个实例单独绑定
正确做法是封装成可观察的状态:
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to load: ${src}`));
img.src = src;
});
}
// 使用
preloadImage('/banner.jpg').then(img => console.log('ready')).catch(err => console.error(err));
CSS content 或 background-image 预加载不可靠
有人把图片地址写进伪元素或未挂载的 class 里,指望浏览器提前拉取——这依赖渲染引擎行为,Chrome 可能做,Safari 可能跳过,Firefox 可能延迟到样式计算阶段才触发。
- 没有加载完成钩子,无法同步业务逻辑(比如“等所有 banner 图片就绪再显示轮播容器”)
- 图片路径若含动态参数(如
?v=123),缓存策略可能让预加载和实际使用的 URL 不一致 - DevTools 的 Network 面板能看到请求,但
img.complete始终为false,因为你拿不到 DOM 节点引用
现代方案:用 fetch() + cache API 更可控
适合需要精准控制缓存生命周期、或预加载非 <img> 场景(比如后续要用 createImageBitmap 处理的图):
-
fetch(src, { cache: 'force-cache' })确保进 HTTP 缓存,但不触发解码;后续<img src>仍需一次 decode - 若要彻底避免重复解码,得配合
response.arrayBuffer()+createImageBitmap(),但兼容性要注意:Safari 16.4+ 才支持createImageBitmap传入ArrayBuffer - 注意
fetch不受<img>的 referrer 策略影响,跨域图需配mode: 'cors',否则缓存无效
示例:
async function preloadWithFetch(src) {
try {
const res = await fetch(src, { cache: 'force-cache', mode: 'cors' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
await res.arrayBuffer(); // 触发下载并缓存
} catch (err) {
console.warn('Preload via fetch failed:', err);
}
}
真实项目中必须检查的三个边界点
预加载代码上线后出问题,八成卡在这三处:
-
src路径拼错或含未 encode 的中文/空格,导致 404 —— 用encodeURIComponent()包一层再发请求 - 预加载队列过长(比如一次 preloader 100 张图),浏览器并发限制(通常 6 个)会让后半段严重延迟 —— 加个简单的并发控制器,比如最多同时 4 个
Promisepending - SPA 路由切换后,上个页面预加载的图片在新页面没被复用 —— 检查是否用了带 hash 的路径(
/a.jpg?v=abc123),相同内容不同 URL 就是不同缓存键
预加载不是写一次就完事,它和你的部署路径、CDN 缓存策略、甚至用户设备的内存压力都有关。最稳的做法:只预加载首屏强依赖的 2–3 张图,其余交给 loading="lazy" 和浏览器原生优先级调度。











