用requestAnimationFrame实现的滚动动画为什么偶尔会卡顿?

玉佩 Dev 阅读 14

在做页面导航平滑滚动时,我用requestAnimationFrame写了个滚动函数,但偶尔会出现动画卡顿的情况,特别是在低端设备上。代码逻辑是这样的:


function smoothScroll(target) {
  const step = () => {
    const diff = target - window.scrollY;
    if (Math.abs(diff) < 0.5) return;
    window.scrollBy(0, diff * 0.1);
    requestAnimationFrame(step);
  };
  requestAnimationFrame(step);
}

我尝试过把diff * 0.1改成更小的系数,也调整过raf的调用时机,但问题依旧。特别是当快速连续点击导航栏时,滚动会突然变慢甚至停顿几帧。这是不是跟浏览器渲染队列有关?有没有更好的优化方法?

我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
爱学习的之芳
你这个问题主要是因为快速连续触发滚动时,多个动画帧叠加导致的性能问题。每次点击导航栏都会启动一个新的动画循环,而之前的动画可能还没结束,这就造成了冲突。

我们可以通过加一个状态锁来解决这个问题,确保同一时间只有一个滚动动画在运行。另外,你的算法里用的是相对滚动 window.scrollBy,这确实容易出现累积误差,建议改成绝对定位的方式。

给你一个改进版本:

let isScrolling = false;

function smoothScroll(target) {
if (isScrolling) return;
isScrolling = true;

const start = window.scrollY;
const distance = target - start;
const duration = 500; // 500ms
const startTime = performance.now();

const step = (currentTime) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress * (2 - progress); // 简单的缓动公式

window.scrollTo(0, start + distance * ease);

if (progress < 1) {
requestAnimationFrame(step);
} else {
isScrolling = false;
}
};

requestAnimationFrame(step);
}


这里做了几个关键改动:首先是加了 isScrolling 锁,防止重复触发;其次是用了绝对定位 window.scrollTo,避免累积误差;最后加入了简单的缓动效果,让动画更平滑。

要注意的是,在实际项目中,你还得考虑一些边界情况,比如用户手动滚动时应该中断动画,或者目标元素被删除的情况要做异常处理。这些都是潜在的安全隐患点,防止因为异常导致页面崩溃。

对了,如果你发现低端设备上还是不够流畅,可以考虑把 duration 时间缩短到300ms左右,这样虽然动画快了点,但能显著提升响应速度。性能和体验有时候就是个权衡的过程。
点赞
2026-02-16 06:06