onscroll事件应绑定在window或document.scrollingElement上,而非<body>;滚动百分比计算公式为scrollTop/(scrollHeight-clientHeight),需用scrollingElement适配;进度条推荐用transform: scaleX()实现并配合requestAnimationFrame节流。

onscroll 事件绑定在哪?别直接写在 <body> 上
现代浏览器中,<body> 元素本身通常不直接触发 scroll 事件——实际滚动容器是 document.documentElement(即 <html>)或 document.body,取决于渲染模式(是否启用怪异模式)。直接给 <body onscroll="..."> 写内联 handler 基本无效,尤其在标准模式下。
正确做法是监听 window 或 document.scrollingElement:
-
window.onscroll = () => { ... }最简单,兼容性好,但会覆盖其他 scroll 监听器 -
document.scrollingElement?.addEventListener('scroll', handler)更规范,能保留多个监听器,且自动适配<html>或<body> - 避免用
document.body.addEventListener('scroll', ...)—— 在多数标准页面里它根本不会触发
怎么算滚动百分比?关键看 scrollTop 和 scrollHeight
滚动进度 = scrollTop / (scrollHeight - clientHeight)。注意分母不是 scrollHeight,而是「可滚动高度」,即内容总高减去视口高。
常见错误:用 document.body.scrollHeight 计算,但在标准模式下应优先用 document.scrollingElement:
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
const el = document.scrollingElement || document.documentElement;const progress = el.scrollTop / (el.scrollHeight - el.clientHeight);- 结果是 0~1 的小数,乘 100 得百分比
- 如果页面内容不够高(
scrollHeight ≤ clientHeight),分母为 0,需 guard:加Math.max(1, ...)或提前 return
进度条 DOM 怎么插?用固定定位 + transform: scaleX() 更高效
不要用 JS 频繁修改 width 百分比(触发布局重排),推荐用 CSS transform 缩放一个细条:
<div id="progress-bar" style="position: fixed; top: 0; left: 0; height: 4px; background: #4a6fa5; width: 100%; transform-origin: left; z-index: 9999;"></div>
JS 中只需更新 transform: scaleX(...):
bar.style.transform = `scaleX(${progress})`;- 比改
width更轻量,纯合成层操作 - 初始设
scaleX(0),滚动时平滑过渡:加transition: transform 0.2s ease-out;到样式里 - 别忘了在 CSS 里隐藏滚动条干扰:
body { margin: 0; },否则clientHeight可能含滚动条宽度误差
为什么滚动卡顿?节流是必须的
scroll 事件每秒可能触发几十次,不做节流会导致 CPU 过载、动画掉帧。不能只靠 CSS transition 补救。
- 用
requestAnimationFrame是最简节流方案:let ticking = false;<br>function updateProgress() {<br> if (!ticking) {<br> requestAnimationFrame(() => {<br> const progress = ...;<br> bar.style.transform = `scaleX(${progress})`;<br> ticking = false;<br> });<br> ticking = true;<br> }<br>} - 避免用
setTimeout或 Lodash 的throttle—— 容易和 RAF 同步冲突,且增加依赖 - 移动端要注意:iOS Safari 的
scroll事件默认非连续触发,需加overflow-scrolling: touch或监听touchmove补充(但本例仅需基础进度条,通常够用)
真正容易被忽略的是:滚动容器可能不是主文档——比如用了 overflow: auto 的局部 div。这时要换监听目标,而不是硬套 window。


















