requestAnimationFrame 限制高频事件的核心是将回调挂起至下一帧执行且帧内去重,天然对齐浏览器60fps渲染节奏,避免跳帧、节省后台资源并防止布局抖动。
用
限制高频事件(如
、
、
)的触发频率,核心思路是:**把事件回调“挂起”到下一帧执行,并在新帧到来前忽略重复触发**。它不是节流(throttle)或防抖(debounce)的替代品,而是一种更贴合浏览器渲染节奏的优化方式,尤其适合需要与视觉更新同步的操作。
为什么 rAF 比 setTimeout 节流更合适?
浏览器每秒约绘制 60 帧,即每帧约 16.7ms。
会把回调安排在**下一次重绘之前执行**,天然对齐渲染时机。相比固定毫秒数的
节流:
不会因 JS 执行阻塞而跳帧或累积延迟
在页面非激活(如切到其他 tab)时自动暂停,节省资源
动画类操作(如滚动位置读取+样式更新)能避免布局抖动(layout thrashing)
基础实现:单次帧内去重
适用于只需确保同一帧最多执行一次的场景(例如更新 UI 状态、记录鼠标位置):
关键点:
标志位 + rAF 组合**,保证多次触发只排队一个回调。
进阶用法:带参数透传与取消能力
当需要传递事件对象、或支持中途取消时,可封装成可复用函数:
注意:
该模式不保留每次事件的细节,只保留最后一次参数**——适合“以最新状态为准”的场景(如拖拽跟随、视差滚动)。
和 CSS 或现代 API 配合效果更好
rAF 的优势在结合其他浏览器机制时更明显:
读取 DOM 尺寸/位置(
)放在 rAF 回调里,避免强制同步布局
写入样式(如
)也放同一 rAF 中,让浏览器合并渲染
搭配
或
可进一步减少对事件监听的依赖
例如滚动中计算元素可见性,优先用
;仅当需像素级控制时,再用 rAF 包裹
回调。
requestAnimationFramescrollresizemousemoverequestAnimationFramesetTimeoutlet isScheduled = false;
function handleScroll() {
// 实际业务逻辑,比如更新某个元素的 top 值
updateUIBasedOnScroll();
isScheduled = false;
}
function scrollListener() {
if (!isScheduled) {
isScheduled = true;
requestAnimationFrame(handleScroll);
}
}
window.addEventListener('scroll', scrollListener);
function rafThrottle(fn, options = {}) {
let scheduled = false;
let lastArgs = [];
return function(...args) {
lastArgs = args;
if (!scheduled) {
scheduled = true;
requestAnimationFrame(() => {
fn.apply(this, lastArgs);
scheduled = false;
});
}
};
}
// 使用
const throttledHandleMouseMove = rafThrottle((e) => {
console.log('Mouse at:', e.clientX, e.clientY);
});
window.addEventListener('mousemove', throttledHandleMouseMove);
getBoundingClientRectelement.style.transformIntersectionObserverResizeObserverIntersectionObserverscroll