
你有没有过这种经历?
产品经理给你看了一个竞品的应用,用户滑动、点击、切换页面时,整个界面都在流畅地呼吸——过渡自然、交互顺滑。然后回头看自己的项目,界面响应虽然不慢,但总觉得少了点什么。
一个同事曾经这样评价我之前做的一个 React 后台系统:"功能不错,但感觉就像是一个 Excel 表格。"
Excel 表格。
那一刻我意识到,UI 动画根本不是锦上添花,而是基础体验的一部分。
问题是,如何在 React 中优雅地实现这些动画?纯 CSS?事件回调?还是第三方库?
先说个事实:很多开发者其实被 CSS animations 限制住了。
我见过太多项目,动画逻辑散落在各个 CSS 文件里,要改个时序都得挨个排查。特别是当你需要:
纯 CSS 会变成一场"CSS 地狱"的修罗场。
尤其是在 Next.js 这样的框架里,页面卸载时如果没有好的过渡处理,用户体验一下子就掉下来了。
我之前在做一个数据平台时,正是因为过渡动画处理不当,导致每次切换页签都有 0.3 秒的白屏闪烁——虽然时间短,但用户反馈里明确指出"界面不够流畅"。

Framer Motion 的核心优势不在于"能做什么",而在于"怎么做"——它把动画声明式化了。
对比一下:
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: slideIn 0.3s ease-out;
}
// React 中得手动控制
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
setIsVisible(true);
}, []);
// CSS 类名判断,逻辑散落...
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
我是一张卡片
</motion.div>
一眼看清楚:初始状态是什么,最终状态是什么,怎么变。 这就是声明式的力量。
但我得强调一点——这不是说 CSS 过时了。
我见过有人在项目里把最简单的 hover 效果也用 Framer Motion,结果包体积莫名其妙大了 30KB。这就过头了。
使用场景划分应该是这样的:
场景 | 推荐方案 |
|---|---|
简单 hover、focus 反馈 | CSS 就够 |
同时控制多个元素的串联动画 | Framer Motion |
响应用户手势(拖拽、滑动) | Framer Motion |
页面/模态框进出场动画 | Framer Motion |
复杂的基于滚动的动画 | Framer Motion |
Framer Motion 真正的价值,在于简化复杂场景下的动画编排逻辑。
// initial:组件挂载时的初始状态
// animate:目标状态
// exit:组件卸载前的退出状态(配合 AnimatePresence)
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.4 }}
>
内容
</motion.div>
这背后的原理是:Framer Motion 在内部维护了一份完整的状态树,当 props 变化时,自动计算中间帧并驱动重排。相比 CSS transitions 只能基于单一属性变化,这种方式对 React 组件的生命周期更友好。
在大型项目里,如果每个组件都硬编码动画参数,维护成本会很高。Framer Motion 的 Variants 解决了这个问题:
// 定义一组可复用的动画组合
const cardVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.3 }
},
hover: { y: -4, boxShadow: "0 10px 25px rgba(0,0,0,0.1)" }
};
// 在多个组件中复用
<motion.div
variants={cardVariants}
initial="hidden"
animate="visible"
whileHover="hover"
>
支持 Variants 的组件
</motion.div>
这样做有几个好处:
在阿里巴巴国际站的一个项目里,我们就是用这套 Variants 模式,让 20+ 个相似组件的动画保持一致性,减少了 60% 的重复代码。
这是 Framer Motion 最黑科技的部分:
<motion.div layout>
{items.map(item => (
<motion.div key={item.id} layout>
{item.name}
</motion.div>
))}
</motion.div>
当数组顺序改变时,Framer Motion 会自动计算每个元素从旧位置到新位置的动画。不需要你手动计算坐标,不需要 CSS 类名,一个 layout 属性就搞定。
原理是通过 FLIP 算法(First, Last, Invert, Play):先记录元素的旧位置,再记录新位置,然后反向计算应该从哪里开始动画。这种做法性能很好,因为它不重排 DOM,只改变 transform。
前面提到的"白屏闪烁"问题,用 Framer Motion 怎么解决?
// pages/dashboard/[id].js
import { AnimatePresence, motion } from'framer-motion';
import { useRouter } from'next/router';
exportdefaultfunction Dashboard({ data }) {
const router = useRouter();
return (
<AnimatePresence mode="wait">
<motion.div
key={router.asPath} // 路由变化时重新挂载
initial={{opacity:0, y:10 }}
animate={{opacity:1, y:0 }}
exit={{opacity:0, y:-10 }}
transition={{duration:0.3 }}
>
<DashboardContent data={data} />
</motion.div>
</AnimatePresence>
);
}
关键点:
exit 动画:旧页面淡出,视觉上"平滑过渡"而不是"突然切换"效果:从用户角度看,页面之间的切换是连贯的、有逻辑的,而不是突兀的跳转。
这一改进后,用户反馈里就再没有"界面不流畅"的吐槽了。
有一次我看到一个项目,几乎每个元素都在动画——按钮缩放、卡片旋转、文字淡入。结果呢?用户感觉很累。
正确做法:动画应该是有意图的。用来:
而不是为了"看起来酷"。
Framer Motion 确实很强大,但也要知道它的成本:
// ❌ 坏例子:在列表中的每一项都有复杂动画
{items.map(item => (
<motion.div
key={item.id}
animate={{
x: item.position,
rotate: item.angle,
opacity: item.visible ? 1 : 0
}}
transition={{ type: "spring", stiffness: 300 }}
>
{item.content}
</motion.div>
))}
// 列表有 1000 项?性能灾难。
正确做法:
shouldReduceMotion 检测用户偏好这是个常见的争议:Framer Motion vs CSS animations。
我的观点是:它们不是对立的。
维度 | Framer Motion | CSS Animations |
|---|---|---|
学习成本 | 低(声明式) | 中等(需要理解 keyframes) |
与 React 集成 | 极好 | 需要手动管理 className |
复杂交互 | 强 | 弱 |
文件体积 | +40KB gzip | 0(原生) |
性能 | 好(GPU 加速) | 更好(浏览器优化) |
选择标准应该是:简单的、纯视觉的动画用 CSS;复杂的、交互驱动的动画用 Framer Motion。
如果你现在还在用纯 CSS 或者 jQuery 时代的动画方案处理 React 的复杂交互,你在浪费开发时间,同时用户也能感受到体验的差异。
Framer Motion 不仅仅是个"动画库",它是把动画从 CSS 地狱里解放出来,纳入 React 组件的思维模型里。
一旦你用过 Variants、Layout Animations 这样的特性,就很难再接受之前的做法了。
你怎么看?有在 React 项目里用过 Framer Motion 吗?或者你坚持纯 CSS 方案?欢迎在评论区分享你的实战经验。