Framer Motion 手势技能用于实现手势驱动的动画:拖拽、平移、点按、悬停、聚焦或触摸交互。当用户询问拖拽、交互元素或 Framer Motion 中基于手势的 UI 时适用。相关技能:核心动画使用 framer-motion-core;变体使用 framer-motion-variants;布局动画使用 framer-motion-layout。
拖拽(Drag) 通过 drag 属性启用拖拽:
<motion.div drag="x" dragConstraints={{ left: -100, right: 100 }} whileDrag={{ scale: 1.1, cursor: "grabbing" }} />
拖拽属性
| 属性 | 类型 | 描述 |
|---|---|---|
| drag | bool | "x" | "y" | 在指定轴上启用拖拽 |
| dragConstraints | object | RefObject | 移动约束 |
| dragMomentum | boolean | 释放后继续惯性(默认:true) |
| dragElastic | number | object | 边界的弹性(默认:0) |
| dragTransition | Transition | 惯性的弹簧配置 |
| whileDrag | MotionProps | 拖拽过程中的动画 |
| onDrag | function | 拖拽过程中的回调 |
| onDragStart | function | 拖拽开始时的回调 |
| onDragEnd | function | 拖拽结束时的回调 |
拖拽约束
<motion.div drag dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }} />
基于 ref 的约束
function Draggable() { const constraintsRef = useRef(null);
return ( <> <motion.div ref={constraintsRef} style={{ width: 500, height: 500, backgroundColor: "#eee" }} /> <motion.div drag dragConstraints={constraintsRef} /> </> ); }
平移(Pan) 平移与拖拽类似,但用于指针/触摸:
<motion.div drag="x" onDrag={(e, info) => { console.log("Position:", info.point); console.log("Velocity:", info.velocity); }} />
平移与拖拽的区别
- 拖拽:带视觉反馈、惯性和约束的鼠标/触摸交互
- 平移:更低层级的指针跟踪,无惯性
点按(whileTap) 按下时的动画:
<motion.button whileTap={{ scale: 0.95, opacity: 0.8 }} whileHover={{ scale: 1.05 }}
Click me </motion.button>
悬停(whileHover) 悬停时的动画:
<motion.div whileHover={{ scale: 1.1, backgroundColor: "#ff0000" }} style={{ width: 100, height: 100, backgroundColor: "#00ff00" }} />
onHoverStart / onHoverEnd
<motion.div onHoverStart={() => console.log("Hover started")} onHoverEnd={() => console.log("Hover ended")} />
聚焦(whileFocus) 聚焦时的动画(键盘):
<motion.input whileFocus={{ scale: 1.05, borderColor: "#00ff00" }} style={{ borderWidth: 2 }} />
带弹簧物理的拖拽
<motion.div drag="x" dragConstraints={{ left: -200, right: 200 }} dragTransition={{ type: "spring", stiffness: 300, damping: 20, mass: 1 }} />
带约束的拖拽控制
function DraggableBox() { const constraintsRef = useRef(null);
return ( <> <motion.div ref={constraintsRef} style={{ width: 500, height: 500, backgroundColor: "#f0f0f0" }} /> {[1, 2, 3].map(i => ( <motion.div key={i} drag dragConstraints={constraintsRef} dragMomentum={false} whileDrag={{ scale: 1.1 }} /> ))} </> ); }
滑动检测
function Swipeable() { const x = useMotionValue(0); const { scrollYProgress } = useScroll();
const opacity = useTransform(x, [-100, 0, 100], [0, 1, 0]);
return ( <motion.div style={{ x, opacity }} drag="x" dragConstraints={{ left: 0, right: 0 }} dragElastic={1} /> ); }
手势状态信息 回调接收 PointInfo 和 DragInfo:
onDrag={(e, info) => { info.point // { x, y } 位置 info.velocity // { x, y } 速度 info.offset // { x, y } 相对起点的偏移 }}
最佳实践
- 使用 dragConstraints 使元素保持在边界内。
- 使用 dragElastic 实现回弹效果。
- 使用 whileDrag 在拖拽过程中提供视觉反馈。
- 使用 dragMomentum 实现自然的延续。
- 使用带 spring 的 dragTransition 实现基于物理的拖拽。
- 在容器内拖拽时,使用 ref 作为约束。
避免
- 若元素应保持在边界内,不要在没有 dragConstraints 的情况下使用 drag。
- 不要忘记拖拽事件只有在指针移动超过阈值后才会触发。
- 不要使用过大的 dragElastic,可能导致视觉故障。
- 不要在拖拽期间动画化冲突属性(例如 scale 与 x)。
了解更多 https://www.framer.com/motion/gestures/ https://www.framer.com/motion/drag/