vue 3 中应使用节流(如 lodash.throttle 或 requestanimationframe)处理滚动事件,避免高频触发导致性能问题;需正确绑定响应式数据、添加 passive 选项、缓存布局属性,并在组件卸载时解绑和清理定时器。

在 Vue 3 中处理滚动事件时,直接监听 window.scroll 并在回调中执行 DOM 查询或状态更新,很容易因高频触发(每秒数十次)导致卡顿、重排重绘过多、CPU 占用飙升。节流(Throttle)是解决该问题的首选策略——它确保函数在指定时间间隔内最多执行一次,既保留响应性,又守住性能底线。
核心思路:用节流包装滚动处理逻辑
不把原始处理函数直接传给 addEventListener,而是先用节流函数包裹,再绑定。关键在于:节流后函数仍能访问组件内部的响应式数据(如 ref、computed),且无需手动清理定时器(若用自定义实现需注意闭包引用)。
- 推荐使用
lodash.throttle:简洁可靠,自动绑定this,支持cancel()和flush() - 也可手写轻量节流:适合无外部依赖场景,控制更细(例如结合
requestAnimationFrame) - 避免在
handleScroll内做耗时操作:如反复调用getBoundingClientRect()、修改大量 DOM、触发深度 watch
标准实现(基于 lodash)
安装并导入:
npm install lodash.throttle
在组件中使用:
import { throttle } from 'lodash'
const handleScroll = () => {
const scrollTop = window.scrollY;
if (scrollTop > 100) {
isFixed.value = true;
} else {
isFixed.value = false;
}
};
// 节流包装,200ms 内最多执行一次
const throttledScroll = throttle(handleScroll, 200);
onMounted(() => {
window.addEventListener('scroll', throttledScroll);
});
onUnmounted(() => {
window.removeEventListener('scroll', throttledScroll);
throttledScroll.cancel(); // 清除可能残留的定时器
});
进阶优化:用 requestAnimationFrame 实现更顺滑节流
对于需要与屏幕刷新率同步的视觉反馈(如吸顶导航的 position 计算),用 requestAnimationFrame 替代 setTimeout 更精准:
let rafId = null;
const rafThrottle = (fn) => {
return () => {
if (!rafId) {
rafId = requestAnimationFrame(() => {
fn();
rafId = null;
});
}
};
};
const handleScroll = () => {
// 此处只做轻量计算,如更新 ref 或触发 computed
scrollY.value = window.scrollY;
};
const throttledScroll = rafThrottle(handleScroll);
避坑提醒
-
别在节流函数里直接解构响应式对象:如
const { value } = isFixed会丢失响应性,应始终通过.value访问 -
移动端需加 passive: true:提升滚动流畅度,但注意 passive 下无法调用
e.preventDefault():window.addEventListener('scroll', throttledScroll, { passive: true }) -
避免在节流回调中频繁读取 offsetTop/offsetHeight:这些属性会强制同步布局计算,建议缓存或用
getBoundingClientRect()配合防抖判断临界点 - 组件卸载时务必解绑 + cancel:否则可能引发内存泄漏或对已销毁组件的状态赋值报错
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










