index.html怎样添加网页加载进度条?

作者:袖梨 2026-06-16
用 document.readyState 判断加载阶段比监听 load 更早,因其可在 'loading' 阶段介入并显示进度条,而 load 事件触发时资源已全部加载完毕。

document.readyState 判断加载阶段比监听 load 更早

很多人直接监听 window.addEventListener('load', ...),但这时页面资源(图片、字体等)已全部下载完,进度条往往一闪而过。真正想显示“正在加载”的过程,得从 HTML 解析开始介入。document.readyState 有三个值:'loading'(DOM 构建中)、'interactive'(DOM 就绪,脚本可能还在执行)、'complete'(全部资源加载完毕)。在 'loading' 阶段插入进度条 DOM,并配合定时器模拟进度,才能让用户感知到“还没好”。

实操建议:

立即学习“前端免费学习笔记(深入)”;

  • 把进度条 <div id="progress-bar"></div> 放在 <body> 最顶部,避免被其他样式遮挡或阻塞渲染
  • 用内联 CSS 控制初始状态(比如 position: fixed; top: 0; height: 3px; width: 0%; background: #4a6fa5; z-index: 9999;),确保不依赖外部样式表加载
  • <head> 里立即执行一段 <script>,检查 document.readyState 并启动进度逻辑,不要等 DOMContentLoaded

requestAnimationFrame 更新宽度比 setTimeout 更平滑

单纯用 setTimeout 每 50ms 加 5%,容易卡顿或跳变,尤其低端设备。浏览器对 requestAnimationFrame 的调度更贴合刷新率,能保证每帧只更新一次宽度,视觉上更自然。

实操建议:

立即学习“前端免费学习笔记(深入)”;

  • 起始进度设为 10%,避免从 0% 开始太突兀;加载中匀速增至 90%,留出“收尾感”
  • document.readyState === 'complete' 时,触发最终 90% → 100% 的动画(可用 CSS transition 或 rAF),然后 300ms 后移除进度条元素
  • 务必在进度条 DOM 插入后、开始动画前,调用 getBoundingClientRect() 或强制触发一次重排(如 offsetHeight),防止浏览器合并样式计算导致首帧不渲染

避免在 asyncdefer 脚本中初始化进度条

如果把进度条逻辑写在 <script async src="progress.js"></script> 里,它可能在 document 还没解析完时就被下载执行,此时 document.body 为空,appendChild 会失败;而 defer 虽然按顺序执行,但已错过 'loading' 阶段。

实操建议:

立即学习“前端免费学习笔记(深入)”;

  • 进度条脚本必须是内联的(<script>...</script>),且放在 <head> 底部或 <body> 顶部
  • 不要依赖第三方库(如 NProgress)做首屏加载条——它们默认在 DOMContentLoaded 后才启动,已经晚了
  • 如果项目用了 Webpack/Vite,别把这段逻辑打进 bundle,它必须独立、最小、零依赖

移动端 Safari 对 position: fixed 进度条有渲染延迟

iOS Safari 在页面滚动或缩放过程中,有时会延迟绘制 fixed 元素,导致进度条“卡住不动”或突然跳到 100%。这不是 bug,是 Safari 对 fixed 定位的优化策略:它会暂缓合成,直到确认不需要频繁重绘。

实操建议:

立即学习“前端免费学习笔记(深入)”;

  • 给进度条加 transform: translateZ(0)will-change: transform,强制启用 GPU 合成层
  • 避免同时设置 top: 0height: 3px 外还加 borderbox-shadow,这些会触发软件渲染
  • 真机测试时,用 Safari 的“开发 > iPhone 模拟器”调试,观察 timeline 中是否出现“Rasterize”长任务

最常被忽略的是:进度条本身不该增加首屏关键路径耗时。一行内联脚本、不到 20 行代码、无网络请求、不阻塞解析——它存在的唯一目的,是让白屏时间显得更短。一旦它开始影响 LCP 或 CLS,就得删掉。

相关文章

精彩推荐