无法监听进度,因其由浏览器预扫描器在html解析早期发起,不暴露xmlhttprequest或fetch实例,故无onprogress回调、不派发progress事件,js无法获取实时字节进度。

HTML 本身没有提供图片预加载的实时字节级进度接口,load 和 error 是离散事件,不是连续流。所谓“检测加载进度”,实际只能做资源计数式估算,或用 fetch + ProgressEvent 主动接管——但后者不适用于 <link rel="preload"> 或 new Image() 的原生行为。
为什么 <link rel="preload"> 无法监听进度
它只是向浏览器发出一个优先级提示,请求由预扫描器(Preload Scanner)在 HTML 解析早期发起,不暴露底层 XMLHttpRequest 或 fetch 实例,因此没有 onprogress 回调、没有 event.loaded/event.total。DevTools 中能看到下载耗时和大小,但 JS 无法读取。
- 即使你写
<link rel="preload" as="image" href="/a.jpg">,JS 里也查不到它的当前字节进度 -
document.querySelector('link[as="image"]')返回的元素没有onprogress属性,也不派发progress事件 - 试图用
PerformanceObserver捕获resource条目,只能拿到完成后的duration和transferSize,不是实时流
用 fetch + ProgressEvent 替代预加载(需放弃 <link>)
如果你真需要百分比数字,唯一可行路径是绕过浏览器预加载机制,改用 fetch 手动拉取,并监听 response.body.getReader().read() 或旧式 XMLHttpRequest.onprogress。但这意味着:你失去 as="image" 的缓存策略、解码优化、以及与 LCP 等指标的对齐。
- 必须设置
mode: 'no-cors'(否则跨域图会失败),但这样拿不到Content-Length,event.total为 0,无法算百分比 - 服务端需明确返回
Content-Length头,且同源或配好 CORS,否则event.total不可用 - 示例关键逻辑:
const xhr = new XMLHttpRequest();xhr.open('GET', '/banner.jpg');xhr.responseType = 'blob';xhr.onprogress = (e) => {if (e.lengthComputable) {const percent = Math.round((e.loaded / e.total) * 100);updateProgressBar(percent);}};xhr.send();
用资源计数法模拟“进度”(最实用方案)
绝大多数生产环境采用此法:把“进度”定义为「已触发 load 事件的关键资源数 / 预设总数」。它不反映真实字节,但用户感知接近,且稳定、轻量、兼容性好。
- 先明确你要计入的资源:比如首屏 8 张
<img>、2 个<link rel="stylesheet">、1 个核心<script></script>→ 总数 = 11 - 每张图用
new Image()加载时,必须先绑定onload/onerror,再赋值src;若img.complete === true,立即计入成功 - 用
Promise.allSettled([...])控制并发(如每次最多 4 个),避免触发浏览器并发限制(通常 6~8 个)导致部分请求被挂起 - 务必加超时兜底:
setTimeout(() => resolve(), 10000),防止某张图因 CDN 故障卡死整个进度条
别踩这些坑
很多人以为加了 loading="eager" 或 decoding="async" 就能拿到进度,其实它们和下载时机完全无关。loading="lazy" 更是反向操作——它会让 Chrome 延迟到滚动前 500px 才发起请求,跟预加载目标冲突。真正的进度感知只发生在你主动控制请求生命周期时,而浏览器原生预加载(<link rel="preload"> 或预扫描发现的 <img src>)天生就是“黑盒”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











