原生<progress>标签需手动更新value才能显示进度,正确写法是el.value=30而非setAttribute;必须设max="100"并用Math.round()处理精度;推荐PerformanceObserver按资源耗时占比计算,Fallback方案可用关键资源数量比例,并加超时兜底。
原生 <progress> 标签不能自动显示页面加载进度,它只是个空壳;真要让百分比动起来,必须自己跟踪资源、计数、更新 value —— 否则你看到的永远是静止的 0%。
<progress value="0"> 什么也不发生浏览器不把 HTML 解析、CSS 下载、JS 执行、图片加载这些过程映射到 <progress> 的 value 上。它只认两个数字属性:value 和 max,且必须手动赋值才生效。setAttribute("value", "30") 是无效的——那只是改 HTML attribute,不会触发 DOM 属性更新和渲染;正确写法是 el.value = 30。
常见翻车点:
max="100",依赖默认值,结果后端返回小数(如 0.73)时直接赋值导致进度条溢出或卡死img.onload 回调里直接写 progress.value = count / total * 100,但没做 Math.round(),导致小数精度问题(如 99.99999999999999 卡在 99%)document.readyState 判断,但它只有 "loading"/"interactive"/"complete" 三态,没法线性映射到 0–100%PerformanceObserver 捕获真实资源加载占比这是目前最接近“真实进度”的方案,尤其适合 Chrome 80+ 及现代 Edge。它能拿到每个资源的 startTime 和 duration,结合 navigationStart 可估算相对耗时占比。
立即学习“前端免费学习笔记(深入)”;
关键实操点:
<head> 最早位置初始化,否则会错过首屏 CSS、字体等关键资源initiatorType === "xmlhttprequest" 的条目——那是 API 请求,不属于页面初始加载资源entry.initiatorType === "navigation" || "link" || "script" 的资源(即 HTML、CSS、同步 JS、内联脚本)img、import())需单独监听 load 事件并计入总完成数示例片段(简化):
const observer = new PerformanceObserver((list) => { const entries = list.getEntries().filter(e => e.entryType === "resource" && ["navigation", "link", "script"].includes(e.initiatorType) ); const totalDuration = entries.reduce((sum, e) => sum + e.duration, 0); const loadedDuration = entries.filter(e => e.duration > 0).reduce((sum, e) => sum + e.duration, 0); const percent = Math.round((loadedDuration / Math.max(totalDuration, 1)) * 100); progress.value = Math.min(percent, 100);});observer.observe({ entryTypes: ["resource"] });
当目标环境不支持 PerformanceObserver(如旧版 Safari),或页面资源结构简单(比如以图片为主),可用数量计数法:预列关键资源,监听 load/error 事件。
注意边界条件:
document.querySelectorAll('img, link[rel="stylesheet"], script:not([async]):not([defer])'),忽略 async/defer 脚本(它们不阻塞解析,不应算进“页面加载”主路径)link[rel="preload"] 需单独加入队列,否则进度会提前结束srcset 或 picture 中的多个源,按实际加载的那个算 1 个,别重复计数transform: scaleX() 比 width 更稳的动画写法用 JS 频繁改 width 会触发重排,尤其在低端安卓机上容易掉帧;transform: scaleX(n) 是合成层操作,性能更好。
但要注意兼容细节:
scaleX(0) 有渲染 bug,起始值建议设为 scaleX(0.001)
load 回调里直接改样式,先缓存当前百分比,再用 requestAnimationFrame 统一提交<progress>,它的内部 track 渲染由浏览器控制,无需手动加 transform;但自定义 <div class="bar"> 时,必须走 transform 路线真正难的不是让进度条动起来,而是定义清楚「什么才算加载完成」——是 HTML 解析完?首屏 CSS 生效?还是所有 img 和字体都到位?不同业务目标,监控粒度和兜底策略完全不同。漏掉一个异步资源,或者误判一个 defer 脚本,百分比就失真了。