原生滚动监听可行但需优化:用requestanimationframe节流、优先position:sticky实现吸顶、懒加载和无限滚动用intersectionobserver、移动端监听wheel并设passive:true。

原生滚动监听完全可行,但直接绑 scroll 事件 + 查 DOM + 改样式 = 卡顿标配。真正“无需库”且“不卡”的方案,是把重逻辑移出回调,用浏览器原生机制兜底。
用 requestAnimationFrame 节流代替 setTimeout 防抖
滚动中每秒触发几十次 scroll,setTimeout 节流只能压频率,但无法对齐渲染帧,仍可能丢帧或输入延迟。而 requestAnimationFrame 天然绑定下一帧,同一帧内多次调用只执行最后一次,适合位置读取类操作。
- 必须在
scroll回调里调用requestAnimationFrame,不能在外面提前取window.scrollY - 所有 DOM 查询(如
document.querySelector)、位置计算(如getBoundingClientRect)、样式修改,全部放进requestAnimationFrame回调里 - 示例写法:
let ticking = false; function update() { const y = window.scrollY; // 这里做吸顶判断、懒加载阈值比对等 ticking = false; } window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(update); ticking = true; } });
吸顶效果优先用 position: sticky,不是 JS
90% 的吸顶场景根本不需要监听 scroll。现代浏览器(Chrome 56+ / Firefox 59+ / Safari 15.4+ / Edge 79+)原生支持 position: sticky,零 JS、零性能开销、不依赖滚动计算。
- 必须显式设置
top(或bottom),只写position: sticky不生效 - 父容器不能有
overflow: hidden、transform或will-change,否则 sticky 失效 - 兼容老浏览器(如 IE)才退化到 JS 方案:监听
scroll+window.scrollY对比元素 offsetTop
懒加载和无限滚动交给 IntersectionObserver
用 scroll + scrollHeight 判断是否到底部,会因图片未加载、广告插入、缩放、iOS Safari 渲染延迟等问题频繁失准。而 IntersectionObserver 是浏览器底层调度,异步、低优先级、不阻塞渲染,且不依赖任何滚动位置计算。
- 在列表末尾放一个占位
<div id="sentinel"></div>,高度设为1px - 初始化时配
rootMargin: '0px 0px 200px 0px',让其在视口底部前 200px 就触发 - 回调里必须检查
entries[0].isIntersecting === true才加载,避免重排误判 - 加载完立刻
observer.unobserve(sentinel),插入新内容后再observer.observe(sentinel)—— 不删 DOM,复用即可
移动端必须加 { passive: true } 并监听 wheel
安卓 WebView 和 iOS Safari 默认把 scroll 设为 passive,若你在回调里调用 event.preventDefault(),控制台会报 warning 且失效。真要阻止默认滚动(比如自定义滚动动画),得显式声明 { passive: false };但更推荐监听 wheel 事件,它更可控、方向明确(event.deltaY)、且默认允许 preventDefault。
- 监听
wheel时,event.deltaY > 0表示向下滚,表示向上滚 - 如需平滑滚动,用
window.scrollBy({ top: amount, behavior: 'smooth' }),别手动做定时器动画 - 确保目标容器有固定高度 +
overflow-y: auto,否则wheel在子容器上不触发
真正难的不是“怎么监听”,而是“监听之后不做啥”——DOM 查询、样式写入、网络请求,全得挪到安全时机;IntersectionObserver 和 position: sticky 不是可选项,是现代原生方案的基线配置。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











