Framer Motion — 子技能
包名:motion(v11+,原名 framer-motion)。导入方式:import { motion, AnimatePresence } from "motion/react"
【何时使用 Framer Motion 与其他方案对比】 资料给出以下对比维度:布局动画(Framer Motion 优秀,支持 layoutId;GSAP 需手动;原生 CSS 无法实现);退场动画(Framer Motion 使用 AnimatePresence;GSAP 用时间线反向;原生 CSS 受限,涉及 display);手势(拖拽、悬停)(Framer Motion 原生且声明式;GSAP 需 Draggable 插件;原生 CSS 仅基础能力);滚动驱动(Framer Motion 用 useScroll + useTransform;GSAP 用 ScrollTrigger,功能更强;原生 CSS 用 scroll-timeline);复杂编排(Framer Motion 用 Variants + 传播;GSAP 时间线更灵活;原生 CSS 用 @keyframes);包体积(Framer Motion 树摇后约 50kb;GSAP 核心约 30kb;原生 CSS 0kb);React 集成(Framer Motion 原生、组件优先;GSAP 依赖 refs + useGSAP;原生 CSS 通过 className 切换)。
选型规则:React UI 交互(模态框、Toast、重排、共享布局)用 Framer Motion;复杂时间线、电影式滚动驱动、SVG 变形用 GSAP。
【AnimatePresence — 退场动画】 使用 <AnimatePresence mode="wait"> 包裹条件渲染的 motion 组件。子组件必须有唯一 key(必需,用于标识组件),并设置 initial、animate、exit。mode 取值:"wait" 等待退场完成后才进入(适合页面转场);"sync" 退场与进入同时进行;"popLayout" 立即从文档流移除(适合列表)。onExitComplete 为所有退场动画完成时的回调。
【布局动画】 共享布局:使用 layoutId,元素会在两个位置间“滑动”,例如高亮标签。自动布局:使用 layout,布局变化时自动动画位置与尺寸。可选值:layout="position" 只动位置(不动尺寸);layout="size" 只动尺寸;layout="preserve-aspect" 在过渡中保持比例。
【Variants — 传播与编排】 通过 container 与 item 两套 Variants 实现父子编排。container 的 transition 支持 staggerChildren(子项错开间隔)、delayChildren(延迟子项)、staggerDirection(1 为正常,-1 为反向)。Variants 会自动向 motion 子元素传播,子元素无需再写 initial/animate。
【手势】 支持 whileHover、whileTap、whileFocus。拖拽相关:drag(true 表示 x+y,"x" 仅水平,"y" 仅垂直)、dragConstraints(可传 left/right/top/bottom)、dragElastic(0 为刚性,1 为自由,默认 0.35)、dragSnapToOrigin(回到初始位置),以及 onDragEnd 回调,可通过 info.offset.x 判断滑动方向。
【Motion Values — 无需重渲染的响应式】 useMotionValue 创建动画值;useTransform 做值映射(如 x 映射到 opacity 或颜色);useSpring 做弹簧平滑(可配 stiffness、damping);useScroll 提供 scrollY 与 scrollYProgress,可做视差;也可传 target 与 offset 做元素级滚动追踪(如 offset: ["start end", "end start"])。Motion Values 不会触发 React 重渲染,而是通过 style 直接更新 DOM。
【不要做(Do Not)】
- 不要在回调中无保护地 setState:onUpdate 里直接 setPosition 且 animate 依赖该 state 会造成无限重渲染;应加守卫或用 useMotionValueEvent 监听变化。
- 不要在布局动画中使用不稳定 key:key={Math.random()} 每次渲染都变会破坏布局追踪;应使用来自数据的稳定 key(如 item.id)。
- 不要忘记 AnimatePresence 子元素的唯一 key:缺失时退场动画不会触发。
- 不要用 motion.div 包裹已经动画的组件:父子同时动画同一 transform 轴会冲突;应每个 transform 轴只保留一层动画,或改用 Variants 协调父子。
【分类判断】该子技能面向 React 前端动画与交互实现,故归入前端与建站。