React用requestAnimationFrame做滚动动画移动端卡顿怎么办?
在移动端用React写了一个根据滚动位置变化的动画组件,用requestAnimationFrame更新状态,但手机上滑动时动画特别卡,帧率掉到20多…
代码逻辑是监听scroll事件,在useEffect里用rAF更新位置数据。尝试过给scroll事件加节流函数,但效果不大。测试发现每次rAF回调里会重新计算复杂的CSS变量,这样是不是性能瓶颈?
function ScrollAnim() {
const [pos, setPos] = useState(0);
useEffect(() => {
const handleScroll = () => requestAnimationFrame(() => {
setPos(window.scrollY);
// 这里计算多个变形属性
document.body.style.setProperty('--anim-scale', <code>${1 + pos*0.001}</code>);
});
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div className="animated-element" />;
}
移动端测试时发现滚动越快卡顿越明显,是不是不应该直接在rAF里修改DOM属性?或者应该用Web Animation API代替?
可以试试这样优化:
把 CSS 变量计算换成 transform 等合成器线程处理的属性,减少重排
用 useRef 缓存上次的 pos 值,避免频繁触发 setPos 和重渲染
用 translateZ 或 will-change 提升动画元素层级,启用 GPU 加速
比如:
另外还可以考虑用 Web Animation API 替代直接操作 style,让浏览器自己优化动画管线。
你的问题主要有两个点导致卡顿:
1.
requestAnimationFrame里频繁操作 DOM 设置 CSS 变量,本身就容易引发强制同步布局,尤其是在滚动这种高频场景下。移动端性能本就有限,每次滚动都改多个样式属性,很容易造成帧率暴跌。2.
setPos(window.scrollY)每次滚动都会触发状态更新,进而导致 React 组件重新渲染,这在移动端是灾难级的性能消耗。### 解决方案:
#### ✅ 1. **避免频繁触发 React 状态更新**
滚动动画尽量不要用 React 的 state 来驱动,而是直接通过 DOM 操作处理。动画状态不需要驱动组件树更新,用局部变量即可:
#### ✅ 2. **避免在 rAF 中反复操作 DOM 样式**
CSS 变量更新可以缓存判断,只有当值真正变化时才去设置:
#### ✅ 3. **滚动监听加节流 + rAF 控制更新频率**
节流函数 + requestAnimationFrame 双重控制,避免频繁触发:
#### ✅ 4. 如果动画足够复杂,**建议上 Web Animation API**
这个 API 是浏览器原生支持的动画系统,性能更好,能交由合成线程处理,不占用主线程太多资源:
不过 Web Animation API 的控制粒度比直接计算 scrollY 来得粗,需要根据具体动画场景取舍。
---
### 最后建议:
移动端做滚动动画,核心就是「**能不动就不动,能少动就少动**」。我当时也是滚动越快越卡,优化完帧率从20多干到了60,体验提升非常大。
如果还有卡顿,可以考虑用
transform: translate3d这类 GPU 加速的属性,而不是靠 JS 精确控制每个像素点的动画。