滚动监听加节流的核心是防止回调阻塞主线程导致卡顿,需设leading:true、trailing:true,延时100–160ms,用快照值替代event,必加passive:true,优先使用IntersectionObserver。

滚动监听加节流,不是为了“让滚动变丝滑”,而是防止监听函数拖慢滚动——滚动本身是否丝滑,取决于浏览器渲染机制和你的回调是否阻塞主线程。关键在控制执行频率、避免重排重绘、配合 passive 选项。
节流参数要设对:leading + trailing 都开
滚动中既要响应“刚进入临界位置”,也要捕获“松手后最后一帧”,所以推荐:
- leading: true —— 第一次触发立刻执行,不错过初始状态判断(比如元素刚进视口)
- trailing: true —— 最后一次触发后仍补调一次,防止因滚动惯性停在边界附近却没触发逻辑
- 等待时间建议 100–160ms(约 6–10 帧),太短起不到节流作用,太长会明显延迟响应
别直接传 event,改用快照值
滚动事件对象是复用的。节流函数里若保存了 event 或 this,等真正执行时,event.scrollingElement.scrollTop 可能已是旧值甚至 null。
- 正确做法:在事件触发瞬间读取并缓存关键值,如
const scrollTop = window.pageYOffset - 把 scrollTop、innerHeight、scrollHeight 这类只读快照作为参数传入节流函数
- 避免在节流回调里再调用
getBoundingClientRect()或反复查 DOM
必须加 passive: true,否则白节流
现代浏览器对未声明 passive: true 的 scroll 监听器,会强制同步执行回调——哪怕你节流了,浏览器仍会卡住滚动线程等你函数跑完。
- 注册监听时务必写:
window.addEventListener('scroll', handler, { passive: true }) - 如果回调里需要调用
preventDefault()(比如自定义滚动拦截),才设passive: false,但这时节流意义不大,应换用其他方案 - 不加 passive,节流最多省点 JS 执行,但滚动卡顿依旧存在
优先用 IntersectionObserver,它自带节流
如果你的需求只是“某元素进入/离开视口就执行”,那 scroll + 节流其实是退而求其次的做法。
- IntersectionObserver 是浏览器原生异步监听,完全不占主线程,也不需要手动节流
- 配置
threshold: [0, 0.1, 0.5]可精准控制触发时机 - 搭配
rootMargin: '0px 0px -50px 0px'实现提前加载,体验更自然 - 仅当需精确获取滚动偏移量(如进度条、视差滚动)时,才回到 scroll 事件 + 节流组合











