motion-react

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motion for React

适用于React的Motion

Package:
motion
(formerly
framer-motion
). Import from
"motion/react"
.
包名:
motion
(原
framer-motion
),从
"motion/react"
导入。

Installation

安装

bash
pnpm add motion
bash
pnpm add motion

Imports

导入方式

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
motion
counterpart:
tsx
<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 元素都有对应的
motion
版本:
tsx
<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:
opacity
,
filter
,
background-image
,
mask-image
.
Independent transforms (not possible in CSS alone):
  • Translate:
    x
    ,
    y
    ,
    z
  • Scale:
    scale
    ,
    scaleX
    ,
    scaleY
  • Rotate:
    rotate
    ,
    rotateX
    ,
    rotateY
    ,
    rotateZ
  • Skew:
    skewX
    ,
    skewY
  • Origin:
    originX
    ,
    originY
    ,
    originZ
Value types: numbers, strings with units (
"100px"
), colors (hex/rgba/hsla),
"auto"
for width/height.
Hardware acceleration: set
transform
directly for GPU compositing:
tsx
<motion.li
  initial={{ transform: "translateX(-100px)" }}
  animate={{ transform: "translateX(0px)" }}
  transition={{ type: "spring" }}
/>
Motion 可以为 任何CSS值 添加动画:
opacity
filter
background-image
mask-image
独立变换属性(原生CSS无法实现):
  • 平移:
    x
    y
    z
  • 缩放:
    scale
    scaleX
    scaleY
  • 旋转:
    rotate
    rotateX
    rotateY
    rotateZ
  • 倾斜:
    skewX
    skewY
  • 变换原点:
    originX
    originY
    originZ
值类型:数字、带单位的字符串(
"100px"
)、颜色(hex/rgba/hsla)、宽高可用
"auto"
硬件加速:直接设置
transform
可启用GPU合成:
tsx
<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
animate
/
initial
/
exit
from parent.
用于编排组合动画的命名动画状态:
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>
变体可以在组件树中传递,子组件会继承父组件的
animate
/
initial
/
exit
属性。

AnimatePresence — 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:
  1. AnimatePresence must stay mounted — never wrap it in a conditional
  2. Direct children must have unique
    key
    props
  3. exit
    prop only works on
    motion
    components inside AnimatePresence
tsx
// WRONG — AnimatePresence unmounts with condition
{show && <AnimatePresence><motion.div /></AnimatePresence>}

// CORRECT — condition inside AnimatePresence
<AnimatePresence>{show && <motion.div key="k" />}</AnimatePresence>
Modes:
"sync"
(default),
"wait"
(sequential enter/exit),
"popLayout"
(pop exiting element out of flow).
Slideshow pattern — change
key
to trigger exit+enter:
tsx
<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
custom
prop +
usePresenceData
:
tsx
<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>
核心规则:
  1. AnimatePresence 必须保持挂载状态 —— 永远不要把它放在条件判断里
  2. 直接子组件 必须有唯一的
    key
    属性
  3. exit
    属性仅在 AnimatePresence 内部的
    motion
    组件上生效
tsx
// 错误写法 —— AnimatePresence会随条件判断一起卸载
{show && <AnimatePresence><motion.div /></AnimatePresence>}

// 正确写法 —— 条件判断放在AnimatePresence内部
<AnimatePresence>{show && <motion.div key="k" />}</AnimatePresence>
模式
"sync"
(默认)、
"wait"
(按顺序进入/退场)、
"popLayout"
(将退场元素从文档流中剥离)。
幻灯片模式 —— 修改
key
来触发退场+入场动画:
tsx
<AnimatePresence mode="wait">
  <motion.img
    key={image.src}
    initial={{ x: 300, opacity: 0 }}
    animate={{ x: 0, opacity: 1 }}
    exit={{ x: -300, opacity: 0 }}
  />
</AnimatePresence>
动态退场数据 —— 通过
custom
属性 +
usePresenceData
传递:
tsx
<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

包体积优化

ApproachSizeWhat you get
motion/react
~34 KBFull API
LazyMotion
+
m
~4.6 KBDeclarative animations, no gestures
motion/react-mini
~2.3 KB
useAnimate
only
tsx
// LazyMotion pattern
import { LazyMotion, domAnimation, m } from "motion/react"

<LazyMotion features={domAnimation}>
  <m.div animate={{ opacity: 1 }} />
</LazyMotion>
方案体积包含功能
motion/react
~34 KB完整API
LazyMotion
+
m
~4.6 KB声明式动画,不含手势功能
motion/react-mini
~2.3 KB仅包含
useAnimate
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:
"user"
(respect OS setting),
"always"
(force instant),
"never"
(ignore).
Hook:
useReducedMotion()
returns
true
when user prefers reduced motion.
tsx
<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>
可选值:
"user"
(遵循操作系统设置)、
"always"
(强制禁用动画)、
"never"
(忽略系统设置)。
Hook:
useReducedMotion()
当用户偏好减少动画时返回
true

Tailwind Integration

Tailwind集成

Let each library handle its strength. Remove Tailwind
transition-*
classes
— they conflict.
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
"motion/react-client"
for optimal tree-shaking:
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

常见问题

  1. Exit animations not firing — AnimatePresence must stay mounted; children need unique
    key
  2. Tailwind transition conflict — Remove
    transition-*
    classes from motion elements
  3. height: "auto"
    +
    display: "none"
    — Use
    visibility: "hidden"
    instead
  4. Layout animations in scrollable containers — Add
    layoutScroll
    prop to scroll parent
  5. Layout animations in fixed elements — Add
    layoutRoot
    prop to fixed parent
  6. Percentage transforms + layout — Convert to pixels; percentage values break FLIP calculation
  7. popLayout
    mode
    — Custom components must use
    forwardRef
    to forward ref to DOM node
  8. AnimatePresence
    propagate
    — Set to
    true
    on nested AnimatePresence to fire child exits
  1. 退场动画不生效 —— AnimatePresence必须保持挂载;子组件需要有唯一
    key
  2. Tailwind过渡冲突 —— 从motion元素上移除
    transition-*
  3. height: "auto"
    +
    display: "none"
    的问题
    —— 改用
    visibility: "hidden"
  4. 可滚动容器内的布局动画 —— 给滚动父元素添加
    layoutScroll
    属性
  5. 固定定位元素内的布局动画 —— 给固定定位父元素添加
    layoutRoot
    属性
  6. 百分比变换 + 布局动画的问题 —— 转换为像素值;百分比值会破坏FLIP计算
  7. popLayout
    模式问题
    —— 自定义组件必须使用
    forwardRef
    将ref传递到DOM节点
  8. 嵌套AnimatePresence的
    propagate
    属性
    —— 嵌套的AnimatePresence需要设置为
    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 ",