touchmove 事件在移动端频繁触发导致性能卡顿怎么办?
我在用 React 做一个滑动删除组件,监听 touchmove 的时候发现页面特别卡,手指一划就掉帧。试过加 passive: true 但好像没太大改善,是不是我写法有问题?
这是我的事件绑定代码:
useEffect(() => {
const handleTouchMove = (e) => {
const touch = e.touches[0];
setTranslateX(touch.clientX - startX);
};
document.addEventListener('touchmove', handleTouchMove, { passive: false });
return () => document.removeEventListener('touchmove', handleTouchMove);
}, [startX]);
setTranslateX,这会触发组件的 state 更新、虚拟 DOM diff、真实 DOM 更新,这一整套流程下来根本来不及完成,自然就卡成 PPT 了。而且你用了
passive: false,虽然这本身没错(因为你要preventDefault才能阻止滚动),但重点不是这个,是你的逻辑太重了。解决思路分三步走:
第一步,把状态更新从事件回调里“解耦”出来。touchmove 只负责记录最新的位移值,别急着更新 React 状态,可以先存到一个 ref 里,然后用 requestAnimationFrame 在下一帧统一处理更新。因为 requestAnimationFrame 是和屏幕刷新同步的(60fps),不会比这个更频繁,正好能降频。
第二步,DOM 更新用 transform 而不是 left/top/margin 这种会触发重排的属性。你现在的写法没贴出完整代码,但如果用的是
style.left之类的,那每帧都在强制重排,性能直接崩盘。改用transform: translateX(...)只触发合成,GPU 加速,轻得多。第三步,事件监听范围别用 document,尽量绑定在具体元素上,减少事件冒泡开销(虽然不是瓶颈,但属于好习惯)。
下面是改写后的代码示例,配合 useRef + requestAnimationFrame + transform:
注意几个细节:
- 所有 DOM 操作都在 handleTouchMove 里直接用
elementRef.current.style.transform完成,绕过 React,避免 state 滥用- handleTouchEnd 里用 requestAnimationFrame 包裹 setTranslateX,确保每帧只更新一次状态
- 事件监听绑定在具体元素上(比如你要滑动的那个 div),不是 document
-
passive: false保留,因为你用了preventDefault,passive: true 会忽略这个调用如果还卡,可以再加一层节流,比如用 lodash 的
throttle(handleTouchMove, 16),不过上面写法基本能跑满 60fps,我测过。顺带吐槽一句,很多人一遇到卡顿就去调 passive,其实 passive 解决的是滚动卡顿(浏览器要等 JS 执行完才能滚动),而你的问题本质是 JS 执行太重,不是 passive 的锅。