实现平滑帧率控制的关键是用闭包配合requestanimationframe(raf)对齐浏览器刷新节奏:闭包隔离各监听器的isscheduled状态,raf确保每帧仅执行一次且天然保帧,再结合passive提升滚动响应。

要实现平滑的帧率控制,关键不是压低执行频次,而是让滚动处理逻辑严格对齐浏览器每帧刷新节奏——闭包在这里承担状态隔离与调度协调的双重角色,而 requestAnimationFrame(rAF)才是真正的帧同步引擎。
为什么必须用闭包管理 rAF 状态
多个滚动监听器共存时,每个都需要独立维护“当前帧是否已调度”的标记。闭包提供天然的私有作用域:
- 每次调用节流工厂函数,都生成专属的
isScheduled变量,互不干扰 - 返回的事件处理器持续引用该变量,生命周期与监听器绑定
- 无需全局 flag 或对象属性,避免卸载后状态残留或跨实例覆盖
rAF 闭包比普通节流更适配视觉更新
固定间隔的 throttle(fn, 16) 无法保证与屏幕刷新严格同步;rAF 则由浏览器调度,在重绘前执行,天然保帧:
- 滚动中持续触发,但闭包确保同一帧只注册一次 rAF 任务
- 回调里只做轻量数据采集(如缓存
scrollTop),把 DOM 更新延迟到 rAF 内执行 - 避免在 rAF 中读取
offsetTop或写入width/height等触发重排的操作
配合 passive 提升原生滚动响应
即使逻辑已优化,事件监听本身也可能阻塞滚动。启用 passive 可交还控制权给浏览器:
- 在支持的浏览器中,
addEventListener('scroll', handler, { passive: true })让滚动不等待 JS 执行 - 注意:若 handler 内需调用
preventDefault()(如自定义滚动),则不能设为 passive - 可结合特性检测自动降级:
'passive' in window.addEventListener ? { passive: true } : false
实际写法:轻量、可复用、无副作用
一个典型实现只需十几行,且不依赖外部库:
- 闭包内声明
let isScheduled = false,每次触发先判断再设为 true - rAF 回调末尾重置
isScheduled = false,为下一帧准备 - 业务逻辑(如 sticky 更新、懒加载判断)单独封装,保持 handler 干净
- 组件销毁时,直接移除事件监听即可,闭包状态随函数引用消失而回收











