
Motion for React 适合处理与组件状态、布局变化和用户手势紧密相关的动画。普通的颜色渐变、悬停效果优先使用 CSS;当动画需要跟随 React 状态、支持退出过程、拖拽或多个元素编排时,再使用 Motion。
本文合并了本站原有的 Motion 入门、motion.div、variants、拖拽、useMotionValue、useTransform 和 AnimatePresence 系列,示例采用当前官方文档中的 motion/react 导入方式。
目录
- 安装与导入方式
- motion 组件的三个基本状态
- 用 variants 管理多个元素
- 悬停、点击与拖拽手势
- useMotionValue 与 useTransform
- AnimatePresence:让元素退出时也有动画
- 性能与可访问性检查
- 如何选择实现方式
- 参考资料
安装与导入方式
新项目安装 motion:
npm install motion
在普通 React 客户端组件中导入:
import { motion } from "motion/react"
在 Next.js App Router 中,包含交互状态和 Hooks 的动画组件需要声明为客户端组件:
"use client"
import { motion } from "motion/react"
export function WelcomeCard() {
return (
<motion.article
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, ease: "easeOut" }}
>
欢迎使用 FreeMac
</motion.article>
)
}
旧项目仍可能使用 framer-motion 包。不要在同一个项目中混用两套导入路径;升级时先根据 Motion 官方升级与 React 文档 检查现有 API。
motion 组件的三个基本状态
motion.div、motion.button 等组件保留原生元素的属性,同时增加动画能力:
initial:元素首次出现时的状态。animate:当前目标状态。transition:持续时间、缓动和弹簧参数。exit:元素离开 React 树时的状态,需要配合AnimatePresence。
<motion.button
initial={{ scale: 0.96, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 320, damping: 24 }}
>
立即开始
</motion.button>
动画两端尽量使用相同值类型,例如从 0px 到 100px。位移和缩放优先使用 x、y、scale、rotate,通常比频繁改变 top、left 或大面积阴影更平滑。
用 variants 管理多个元素
当父子元素共享“隐藏/显示”等语义状态时,用 variants 比在每个元素上重复写对象更清楚:
const list = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.08 },
},
}
const item = {
hidden: { opacity: 0, y: 12 },
visible: { opacity: 1, y: 0 },
}
export function ToolList({ tools }: { tools: string[] }) {
return (
<motion.ul variants={list} initial="hidden" animate="visible">
{tools.map((tool) => (
<motion.li key={tool} variants={item}>
{tool}
</motion.li>
))}
</motion.ul>
)
}
staggerChildren 适合列表依次出现;如果只是单个元素变化,直接使用 animate 会更易读。
悬停、点击与拖拽手势
常见交互由 whileHover、whileTap 和 drag 提供:
<motion.div
drag="x"
dragConstraints={{ left: -120, right: 120 }}
dragElastic={0.15}
whileDrag={{ scale: 1.04, cursor: "grabbing" }}
style={{ cursor: "grab" }}
>
左右拖动
</motion.div>
拖拽元素要有明确边界。移动端还要检查滚动手势是否冲突,并避免让动画成为完成核心任务的唯一方式。
useMotionValue 与 useTransform
MotionValue 用于高频变化的动画值。它可以直接更新样式,而不必让 React 在每一帧重新渲染组件。useTransform 则把一个 MotionValue 的输入范围映射为新的输出值。
"use client"
import { motion, useMotionValue, useTransform } from "motion/react"
export function SwipeCard() {
const x = useMotionValue(0)
const rotate = useTransform(x, [-200, 0, 200], [-12, 0, 12])
const opacity = useTransform(x, [-200, 0, 200], [0.35, 1, 0.35])
return (
<motion.article
drag="x"
dragConstraints={{ left: 0, right: 0 }}
style={{ x, rotate, opacity }}
>
拖动卡片
</motion.article>
)
}
Motion 官方说明,useTransform 既能映射数值、颜色等范围,也能通过转换函数组合多个 MotionValue。只有当数值确实高频变化或需要连续映射时才使用它;普通开关状态继续使用 React state。
AnimatePresence:让元素退出时也有动画
React 条件渲染会立即移除元素。AnimatePresence 会让带有 exit 的元素在离开前完成动画:
"use client"
import { AnimatePresence, motion } from "motion/react"
export function Modal({ open }: { open: boolean }) {
return (
<AnimatePresence>
{open && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.section
initial={{ scale: 0.96, y: 16 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.96, y: 12 }}
>
弹窗内容
</motion.section>
</motion.div>
)}
</AnimatePresence>
)
}
直接子元素必须有稳定且唯一的 key。页面切换需要“先退场、后入场”时,可以使用 mode="wait";列表布局变化更适合结合 layout 或 mode="popLayout"。具体限制以 AnimatePresence 官方文档 为准。
性能与可访问性检查
上线前至少检查以下项目:
- 优先动画
transform和opacity,避免持续触发布局计算。 - 不要给几十个不可见元素同时启动无限动画。
- 弹簧动画出现抖动时,调整
stiffness、damping,而不是叠加多个 transition。 - 使用稳定的列表 key,避免元素被误判为全新节点。
- 尊重用户的“减少动态效果”设置,对大幅位移提供淡入或无动画替代。
- 在低性能设备上实际测试拖拽、滚动和页面切换。
如何选择实现方式
| 场景 | 推荐方式 |
|---|---|
| 简单悬停、颜色和透明度变化 | CSS transition |
| 动画跟随 React 状态 | motion 组件 |
| 列表依次出现 | variants + staggerChildren |
| 拖拽、滚动映射、高频数值 | MotionValue + useTransform |
| 弹窗、路由或列表退出 | AnimatePresence |
| 很长的时间轴或复杂画布动画 | 评估 GSAP、Canvas 等专用方案 |
Motion 负责动画表达,React 仍负责状态来源。对 Hooks 的职责还不确定时,可以继续阅读本站的 React Hooks 完整指南。
参考资料
继续阅读
React Hydration Failed:原因、定位与修复
系统排查服务器 HTML 与客户端首次渲染不一致的问题,覆盖时间、随机数、浏览器 API、无效 HTML、客户端存储、第三方扩展和 suppressHydrationWarning 的边界。
11 分钟Next.js 路由指南:App Router 常见模式
基于当前 Next.js App Router,梳理静态路由、动态路由、catch-all、route groups、parallel routes 和 route handlers 的使用场景,并说明什么时候还会遇到 Pages Router。
10 分钟Next.js Server 与 Client Components 怎么选
解释 App Router 中 Server Component 与 Client Component 的执行位置、数据获取、序列化边界和 hydration,并给出缩小客户端 bundle 的组件拆分方法。
订阅 FreeMac
每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。