motion-react
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotion for React
适用于React的Motion
Package: (formerly ). Import from .
motionframer-motion"motion/react"包名:(原),从 导入。
motionframer-motion"motion/react"Installation
安装
bash
pnpm add motionbash
pnpm add motionImports
导入方式
tsx
// Standard React (Vite, CRA, Pages Router)
import { motion, AnimatePresence } from "motion/react"
// Next.js App Router — use "motion/react-client" for RSC tree-shaking
"use client"
import * as motion from "motion/react-client"
// Minimal bundle (2.3 KB) — imperative API only
import { useAnimate } from "motion/react-mini"
// Reduced bundle (4.6 KB) — LazyMotion + m component
import { LazyMotion, domAnimation, m } from "motion/react"tsx
// 标准React项目(Vite、CRA、Pages Router)
import { motion, AnimatePresence } from "motion/react"
// Next.js App Router — 使用 "motion/react-client" 实现RSC摇树优化
"use client"
import * as motion from "motion/react-client"
// 最小体积包(2.3 KB)—— 仅包含命令式API
import { useAnimate } from "motion/react-mini"
// 精简体积包(4.6 KB)—— 包含 LazyMotion + m 组件
import { LazyMotion, domAnimation, m } from "motion/react"Motion Component
Motion 组件
Every HTML/SVG element has a counterpart:
motiontsx
<motion.div />
<motion.button />
<motion.svg />
<motion.circle />Custom components: wrap with :
motion.create()tsx
const MotionBox = motion.create(Box)
// forwardRef required — the ref must reach a DOM node每个 HTML/SVG 元素都有对应的 版本:
motiontsx
<motion.div />
<motion.button />
<motion.svg />
<motion.circle />自定义组件:用 包裹:
motion.create()tsx
const MotionBox = motion.create(Box)
// 需要使用 forwardRef —— ref 必须能传递到DOM节点Core Animation Props
核心动画属性
tsx
<motion.div
initial={{ opacity: 0, y: 20 }} // mount state (or false to skip)
animate={{ opacity: 1, y: 0 }} // target state
exit={{ opacity: 0, y: -20 }} // unmount state (needs AnimatePresence)
transition={{ type: "spring", bounce: 0.25 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
whileFocus={{ borderColor: "#00f" }}
whileDrag={{ scale: 1.1 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, margin: "-100px" }}
/>tsx
<motion.div
initial={{ opacity: 0, y: 20 }} // 挂载时的状态(设为false可跳过)
animate={{ opacity: 1, y: 0 }} // 目标状态
exit={{ opacity: 0, y: -20 }} // 卸载时的状态(需要配合AnimatePresence使用)
transition={{ type: "spring", bounce: 0.25 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
whileFocus={{ borderColor: "#00f" }}
whileDrag={{ scale: 1.1 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, margin: "-100px" }}
/>Animatable Values
可动画值
Motion animates any CSS value: , , , .
opacityfilterbackground-imagemask-imageIndependent transforms (not possible in CSS alone):
- Translate: ,
x,yz - Scale: ,
scale,scaleXscaleY - Rotate: ,
rotate,rotateX,rotateYrotateZ - Skew: ,
skewXskewY - Origin: ,
originX,originYoriginZ
Value types: numbers, strings with units (), colors (hex/rgba/hsla), for width/height.
"100px""auto"Hardware acceleration: set directly for GPU compositing:
transformtsx
<motion.li
initial={{ transform: "translateX(-100px)" }}
animate={{ transform: "translateX(0px)" }}
transition={{ type: "spring" }}
/>Motion 可以为 任何CSS值 添加动画:、、、。
opacityfilterbackground-imagemask-image独立变换属性(原生CSS无法实现):
- 平移:、
x、yz - 缩放:、
scale、scaleXscaleY - 旋转:、
rotate、rotateX、rotateYrotateZ - 倾斜:、
skewXskewY - 变换原点:、
originX、originYoriginZ
值类型:数字、带单位的字符串()、颜色(hex/rgba/hsla)、宽高可用 。
"100px""auto"硬件加速:直接设置 可启用GPU合成:
transformtsx
<motion.li
initial={{ transform: "translateX(-100px)" }}
animate={{ transform: "translateX(0px)" }}
transition={{ type: "spring" }}
/>Keyframes
关键帧
Pass arrays to animate through a sequence:
tsx
<motion.div animate={{ x: [0, 100, 0] }} />
// null = "use current value"
<motion.div animate={{ x: [null, 100, 0] }} />传入数组可按序列执行动画:
tsx
<motion.div animate={{ x: [0, 100, 0] }} />
// null = "使用当前值"
<motion.div animate={{ x: [null, 100, 0] }} />Variants
变体(Variants)
Named animation states for orchestrated animations:
tsx
const list = {
visible: {
transition: { staggerChildren: 0.1 }
},
hidden: {}
}
const item = {
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 20 }
}
<motion.ul initial="hidden" animate="visible" variants={list}>
<motion.li variants={item} />
<motion.li variants={item} />
</motion.ul>Variants propagate through the tree. Children inherit // from parent.
animateinitialexit用于编排组合动画的命名动画状态:
tsx
const list = {
visible: {
transition: { staggerChildren: 0.1 }
},
hidden: {}
}
const item = {
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 20 }
}
<motion.ul initial="hidden" animate="visible" variants={list}>
<motion.li variants={item} />
<motion.li variants={item} />
</motion.ul>变体可以在组件树中传递,子组件会继承父组件的 // 属性。
animateinitialexitAnimatePresence — Exit Animations
AnimatePresence —— 退场动画
tsx
import { AnimatePresence } from "motion/react"
<AnimatePresence>
{isVisible && (
<motion.div
key="modal" // REQUIRED: unique key
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>Critical rules:
- AnimatePresence must stay mounted — never wrap it in a conditional
- Direct children must have unique props
key - prop only works on
exitcomponents inside AnimatePresencemotion
tsx
// WRONG — AnimatePresence unmounts with condition
{show && <AnimatePresence><motion.div /></AnimatePresence>}
// CORRECT — condition inside AnimatePresence
<AnimatePresence>{show && <motion.div key="k" />}</AnimatePresence>Modes: (default), (sequential enter/exit), (pop exiting element out of flow).
"sync""wait""popLayout"Slideshow pattern — change to trigger exit+enter:
keytsx
<AnimatePresence mode="wait">
<motion.img
key={image.src}
initial={{ x: 300, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -300, opacity: 0 }}
/>
</AnimatePresence>Dynamic exit data — pass via prop + :
customusePresenceDatatsx
<AnimatePresence custom={direction}>
<Slide key={id} />
</AnimatePresence>
// Inside Slide:
const direction = usePresenceData()tsx
import { AnimatePresence } from "motion/react"
<AnimatePresence>
{isVisible && (
<motion.div
key="modal" // 必须:唯一key
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>核心规则:
- AnimatePresence 必须保持挂载状态 —— 永远不要把它放在条件判断里
- 直接子组件 必须有唯一的 属性
key - 属性仅在 AnimatePresence 内部的
exit组件上生效motion
tsx
// 错误写法 —— AnimatePresence会随条件判断一起卸载
{show && <AnimatePresence><motion.div /></AnimatePresence>}
// 正确写法 —— 条件判断放在AnimatePresence内部
<AnimatePresence>{show && <motion.div key="k" />}</AnimatePresence>模式:(默认)、(按顺序进入/退场)、(将退场元素从文档流中剥离)。
"sync""wait""popLayout"幻灯片模式 —— 修改 来触发退场+入场动画:
keytsx
<AnimatePresence mode="wait">
<motion.img
key={image.src}
initial={{ x: 300, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -300, opacity: 0 }}
/>
</AnimatePresence>动态退场数据 —— 通过 属性 + 传递:
customusePresenceDatatsx
<AnimatePresence custom={direction}>
<Slide key={id} />
</AnimatePresence>
// Slide组件内部:
const direction = usePresenceData()Transitions
过渡效果
For full transition API details, see references/transitions-api.md.
Quick reference:
tsx
// Spring (default for physical props: x, y, scale)
transition={{ type: "spring", bounce: 0.25 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
transition={{ type: "spring", visualDuration: 0.5, bounce: 0.25 }}
// Tween (default for opacity, color)
transition={{ duration: 0.3, ease: "easeInOut" }}
// Per-value transitions
transition={{
default: { type: "spring" },
opacity: { duration: 0.2, ease: "linear" }
}}
// Orchestration
transition={{ delay: 0.5, repeat: Infinity, repeatType: "reverse" }}
// Global default
<MotionConfig transition={{ duration: 0.3 }}>完整的过渡API说明请参考 references/transitions-api.md。
快速参考:
tsx
// 弹簧动画(位移、缩放等物理属性的默认效果)
transition={{ type: "spring", bounce: 0.25 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
transition={{ type: "spring", visualDuration: 0.5, bounce: 0.25 }}
// 补间动画(透明度、颜色的默认效果)
transition={{ duration: 0.3, ease: "easeInOut" }}
// 针对单个属性的过渡配置
transition={{
default: { type: "spring" },
opacity: { duration: 0.2, ease: "linear" }
}}
// 编排配置
transition={{ delay: 0.5, repeat: Infinity, repeatType: "reverse" }}
// 全局默认配置
<MotionConfig transition={{ duration: 0.3 }}>Layout Animations
布局动画
For full layout animation details, see references/layout-animations.md.
tsx
// Auto-animate any layout change
<motion.div layout />
// Shared element transitions
<motion.div layoutId="underline" />
// Customize layout transition
<motion.div layout transition={{ layout: { duration: 0.3 } }} />完整的布局动画说明请参考 references/layout-animations.md。
tsx
// 自动为所有布局变化添加动画
<motion.div layout />
// 共享元素过渡
<motion.div layoutId="underline" />
// 自定义布局过渡效果
<motion.div layout transition={{ layout: { duration: 0.3 } }} />Gestures & Drag
手势与拖拽
For full gesture/drag API, see references/gestures-and-drag.md.
tsx
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
drag // enable both axes
drag="x" // constrain to x-axis
dragConstraints={{ left: -100, right: 100 }}
dragElastic={0.2}
/>完整的手势/拖拽API说明请参考 references/gestures-and-drag.md。
tsx
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
drag // 开启双轴拖拽
drag="x" // 限制为x轴拖拽
dragConstraints={{ left: -100, right: 100 }}
dragElastic={0.2}
/>Scroll Animations
滚动动画
For full scroll API, see references/scroll-animations.md.
tsx
// Viewport-triggered
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
/>
// Scroll-linked progress bar
const { scrollYProgress } = useScroll()
<motion.div style={{ scaleX: scrollYProgress }} />
// Element scroll progress
const ref = useRef(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"]
})完整的滚动API说明请参考 references/scroll-animations.md。
tsx
// 视口触发动画
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
/>
// 滚动关联进度条
const { scrollYProgress } = useScroll()
<motion.div style={{ scaleX: scrollYProgress }} />
// 元素滚动进度
const ref = useRef(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"]
})Hooks & Motion Values
Hooks与Motion值
For full hooks API, see references/hooks-and-motion-values.md.
tsx
// Manual motion values (no re-renders)
const x = useMotionValue(0)
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])
<motion.div drag="x" style={{ x, opacity }} />
// Smooth spring following
const springX = useSpring(x, { stiffness: 100, damping: 30 })
// Imperative animation control
const [scope, animate] = useAnimate()
animate("li", { opacity: 1 }, { stagger: 0.1 })
// Event listener (no re-render)
useMotionValueEvent(scrollY, "change", (v) => console.log(v))完整的Hooks API说明请参考 references/hooks-and-motion-values.md。
tsx
// 手动创建motion值(不会触发重渲染)
const x = useMotionValue(0)
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])
<motion.div drag="x" style={{ x, opacity }} />
// 平滑弹簧跟随效果
const springX = useSpring(x, { stiffness: 100, damping: 30 })
// 命令式动画控制
const [scope, animate] = useAnimate()
animate("li", { opacity: 1 }, { stagger: 0.1 })
// 事件监听器(不会触发重渲染)
useMotionValueEvent(scrollY, "change", (v) => console.log(v))Bundle Optimization
包体积优化
| Approach | Size | What you get |
|---|---|---|
| ~34 KB | Full API |
| ~4.6 KB | Declarative animations, no gestures |
| ~2.3 KB | |
tsx
// LazyMotion pattern
import { LazyMotion, domAnimation, m } from "motion/react"
<LazyMotion features={domAnimation}>
<m.div animate={{ opacity: 1 }} />
</LazyMotion>| 方案 | 体积 | 包含功能 |
|---|---|---|
| ~34 KB | 完整API |
| ~4.6 KB | 声明式动画,不含手势功能 |
| ~2.3 KB | 仅包含 |
tsx
// LazyMotion使用模式
import { LazyMotion, domAnimation, m } from "motion/react"
<LazyMotion features={domAnimation}>
<m.div animate={{ opacity: 1 }} />
</LazyMotion>Accessibility
无障碍支持
tsx
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>Options: (respect OS setting), (force instant), (ignore).
"user""always""never"Hook: returns when user prefers reduced motion.
useReducedMotion()truetsx
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>可选值:(遵循操作系统设置)、(强制禁用动画)、(忽略系统设置)。
"user""always""never"Hook: 当用户偏好减少动画时返回。
useReducedMotion()trueTailwind Integration
Tailwind集成
Let each library handle its strength. Remove Tailwind classes — they conflict.
transition-*tsx
// WRONG — Tailwind transition conflicts with Motion
<motion.div className="transition-all duration-300" animate={{ x: 100 }} />
// CORRECT — Tailwind for styling, Motion for animation
<motion.div className="rounded-lg bg-blue-600 p-4" whileHover={{ scale: 1.05 }} />让两个库各司其职,请移除Tailwind的类 —— 它们会产生冲突。
transition-*tsx
// 错误写法 —— Tailwind过渡效果与Motion冲突
<motion.div className="transition-all duration-300" animate={{ x: 100 }} />
// 正确写法 —— Tailwind负责样式,Motion负责动画
<motion.div className="rounded-lg bg-blue-600 p-4" whileHover={{ scale: 1.05 }} />Next.js App Router
Next.js App Router适配
Motion components require client-side rendering. Use for optimal tree-shaking:
"motion/react-client"tsx
// components/motion-client.tsx
"use client"
import * as motion from "motion/react-client"
export { motion }
// app/page.tsx (Server Component)
import { motion } from "@/components/motion-client"
<motion.div animate={{ opacity: 1 }} />Motion组件需要客户端渲染,使用实现最优摇树优化:
"motion/react-client"tsx
// components/motion-client.tsx
"use client"
import * as motion from "motion/react-client"
export { motion }
// app/page.tsx(服务端组件)
import { motion } from "@/components/motion-client"
<motion.div animate={{ opacity: 1 }} />Common Pitfalls
常见问题
- Exit animations not firing — AnimatePresence must stay mounted; children need unique
key - Tailwind transition conflict — Remove classes from motion elements
transition-* - +
height: "auto"— Usedisplay: "none"insteadvisibility: "hidden" - Layout animations in scrollable containers — Add prop to scroll parent
layoutScroll - Layout animations in fixed elements — Add prop to fixed parent
layoutRoot - Percentage transforms + layout — Convert to pixels; percentage values break FLIP calculation
- mode — Custom components must use
popLayoutto forward ref to DOM nodeforwardRef - AnimatePresence — Set to
propagateon nested AnimatePresence to fire child exitstrue
- 退场动画不生效 —— AnimatePresence必须保持挂载;子组件需要有唯一
key - Tailwind过渡冲突 —— 从motion元素上移除类
transition-* - +
height: "auto"的问题 —— 改用display: "none"visibility: "hidden" - 可滚动容器内的布局动画 —— 给滚动父元素添加属性
layoutScroll - 固定定位元素内的布局动画 —— 给固定定位父元素添加属性
layoutRoot - 百分比变换 + 布局动画的问题 —— 转换为像素值;百分比值会破坏FLIP计算
- 模式问题 —— 自定义组件必须使用
popLayout将ref传递到DOM节点forwardRef - 嵌套AnimatePresence的属性 —— 嵌套的AnimatePresence需要设置为
propagate才能触发子元素退场动画true
References
参考文档
- Animation API details: references/animation-api.md
- Transition types & spring config: references/transitions-api.md
- Gestures & drag: references/gestures-and-drag.md
- Layout animations: references/layout-animations.md
- Scroll animations: references/scroll-animations.md
- Hooks & motion values: references/hooks-and-motion-values.md
- 动画API详情:references/animation-api.md
- 过渡类型与弹簧配置:references/transitions-api.md
- 手势与拖拽:references/gestures-and-drag.md
- 布局动画:references/layout-animations.md
- 滚动动画:references/scroll-animations.md
- Hooks与motion值:references/hooks-and-motion-values.md ",