如何让鼠标拖拽触发的Framer Motion动画流畅且避免重渲染?
如何在Framer Motion中实现无重渲染的流畅拖拽步进动画
我正在开发一个鼠标拖拽步进调整盒子大小的功能,使用Framer Motion添加动画时遇到了两难问题:
- 用
useState管理盒子参数时,动画流畅但每次鼠标移动都会触发组件重渲染 - 用
useAnimation避免重渲染时,动画播放过程中鼠标移动会出现卡顿,换成set方法虽然不卡但会丢失动画效果
问题根源
使用useAnimation时卡顿的核心原因是:每次mousemove触发都调用controls.start(),会不断创建新的动画实例、中断正在进行的动画,导致动画队列混乱,最终出现卡顿。
解决方案
通过useRef跟踪当前目标状态,仅当目标状态发生变化时才启动动画,既避免组件重渲染,又保证动画流畅:
import { useRef, type MouseEvent as ReactMouseEvent } from 'react' import { motion, useAnimation } from 'framer-motion' const STEP_SIZE = 40; export default function App() { console.log('render') const controls = useAnimation() // 用ref存储当前目标参数,更新ref不会触发组件重渲染 const currentParams = useRef({ top: 0, height: 0 }) const handlerMouseDown = (e: ReactMouseEvent<HTMLDivElement>) => { const initialPosition = e.clientY const handlerMouseMove = (e: MouseEvent) => { const movement = e.clientY - initialPosition const steps = Math.ceil(Math.abs(movement) / STEP_SIZE) const newTop = movement > 0 ? initialPosition : initialPosition - steps * STEP_SIZE const newHeight = steps * STEP_SIZE // 仅当目标参数变化时启动动画,避免频繁创建动画实例 if (newTop !== currentParams.current.top || newHeight !== currentParams.current.height) { currentParams.current = { top: newTop, height: newHeight } controls.start({ top: newTop, height: newHeight, // 调整动画曲线和时长,适配拖拽步进的手感 ease: "easeOut", duration: 0.15 }) } } const handlerMouseUp = () => { window.removeEventListener('mousemove', handlerMouseMove) window.removeEventListener('mouseup', handlerMouseUp) } window.addEventListener('mousemove', handlerMouseMove) window.addEventListener('mouseup', handlerMouseUp) } return ( <div className="Container" onMouseDown={handlerMouseDown}> <motion.div className="Selector" animate={controls}></motion.div> </div> ); }
方案说明
- 避免重渲染:使用
useRef存储目标参数,更新ref不会触发React组件的重渲染流程 - 流畅动画:仅在计算出的新参数与当前ref存储的参数不一致时,才调用
controls.start()启动动画,避免频繁中断动画 - 手感优化:通过调整
ease和duration参数,可以让步进动画的过渡更自然,匹配拖拽操作的节奏
内容的提问来源于stack exchange,提问作者Unknown X
相关产品推荐
相关产品推荐

