大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
动画与 Framer Motion
React 更新 DOM 很快,但 进出场、布局变化、手势 用 CSS alone 难协调。Framer Motion(现 Motion for React)提供声明式 motion 组件 与 layout 动画,是 React 生态最常用的动画方案之一。
学习目标
- 掌握
motion、AnimatePresence基本用法 - 理解何时用 CSS transition、何时用 Motion
- 避免动画损害性能与无障碍
CSS vs Framer Motion
| 场景 | CSS transition | Framer Motion |
|---|---|---|
| hover 颜色 | ✅ 足够 | 过重 |
| 列表增删 reorder | 难 | layout + AnimatePresence |
| 路由页切换 | 需手写 key | 与 React 生命周期对齐 |
| 拖拽 | 需自己算 | drag prop |
基础过渡
import { motion } from "framer-motion";
export function FadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
{children}
</motion.div>
);
}
initial / animate / exit 三元组是核心;退出动画需外层 AnimatePresence。
列表与 AnimatePresence
import { AnimatePresence, motion } from "framer-motion";
<AnimatePresence mode="popLayout">
{items.map((item) => (
<motion.li
key={item.id}
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{item.title}
</motion.li>
))}
</AnimatePresence>
layout 让 sibling 尺寸变化时 平滑重排,适合侧栏折叠、卡片网格。
手势基础
<motion.div drag="x" dragConstraints={{ left: 0, right: 200 }} />
移动端抽屉、滑块可用;桌面仍以点击为主,别为炫技强加 drag。
性能与 a11y
- 优先动画 transform / opacity,少动
width/height(或配合layout) - 尊重
prefers-reduced-motion:
const shouldReduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
transition={{ duration: shouldReduce ? 0 : 0.3 }}
- 焦点管理:Modal 动画结束后再 trap focus(配合 rxe-ui 的 Dialog)
Next.js 中 motion 组件在 Client Component 内使用;RSC 页面包一层 'use client' 动画壳即可,见 nextjs-essentials。
练习
- 为课时列表项添加入场 fade + stagger(
transition.delay按 index) - 侧栏折叠用
layout观察兄弟元素是否平滑 - 在 DevTools Performance 录一次动画,确认无长任务暴增
小结
Motion 声明「从哪到哪」,React 声明「显示什么」。 下一课 E2E 测试 Cypress 与 Playwright。