framer-motion、popmotion、react-motion 和 react-spring 都是用于在 React 应用中构建动画的流行库,但它们的底层原理、API 设计哲学和适用场景截然不同。framer-motion 是目前生态最完善的声明式动画库,专为 React 设计,提供了强大的布局动画和手势支持。react-spring 基于物理弹簧模型,擅长处理复杂的交互式和连续动画,提供极高的流畅度。popmotion 是一个底层的函数式动画引擎,不依赖 React,适合需要完全控制动画循环的高级场景。react-motion 是早期的弹簧动画库,虽然开创了先河,但目前已停止维护,不再推荐用于新项目。
在 React 生态中,动画不仅仅是让界面“动起来”,它关乎用户体验的流畅度、交互的反馈感以及代码的可维护性。我们今天对比的四个库代表了动画技术的不同演进阶段:从早期的弹簧模型探索,到纯粹的函数式引擎,再到如今高度封装的声明式解决方案。
理解这些库的第一步是看清它们的“身份”。
framer-motion 是一个专为 React 打造的声明式库。它将动画状态直接绑定在组件属性上,你只需要告诉它“最终状态是什么”,它会自动处理过渡。它内部集成了布局测量和手势识别,是典型的“电池included”方案。
// framer-motion: 声明式定义动画
import { motion } from "framer-motion";
function Box() {
return (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
/>
);
}
react-spring 的核心是物理弹簧模型。它不关心“持续时间”,而是关心“质量”、“张力”和“摩擦”。它返回的是动画值的“钩子(hooks)”,你需要将这些值手动绑定到样式上。这种方式给了你极大的控制权,适合复杂的动态交互。
// react-spring: 基于物理弹簧的钩子
import { useSpring, animated } from "@react-spring/web";
function Box() {
const props = useSpring({
from: { opacity: 0, transform: 'scale(0.5)' },
to: { opacity: 1, transform: 'scale(1)' },
config: { mass: 1, tension: 170, friction: 26 }
});
return <animated.div style={props} />;
}
popmotion 不是一个 React 库,而是一个纯粹的 JavaScript 动画引擎。它没有内置的组件,而是提供 action、physics 和 input 等函数式原语。你需要自己编写代码将动画值更新到 DOM 或 React 状态中。这适合需要完全掌控每一帧逻辑的场景。
// popmotion: 纯粹的函数式动画原语
import { action, spring } from "popmotion";
// 定义一个弹簧物理动作
const mySpring = spring({ from: 0, to: 100, velocity: 50 });
// 启动动作并订阅每一帧的值
mySpring.start({
next: (value) => {
// 手动更新 DOM 或 React 状态
element.style.transform = `translateX(${value}px)`;
}
});
react-motion 是早期的弹簧动画库,它引入了 spring 值概念,但 API 设计基于旧的 React 生命周期模式。由于其架构限制和缺乏维护,它已无法适应现代 React 的开发需求。
// react-motion: 已弃用的旧式 API
import { Motion, spring } from "react-motion";
function Box() {
return (
<Motion style={{ x: spring(100) }}>
{({ x }) => <div style={{ transform: `translateX(${x}px)` }} />}
</Motion>
);
}
当列表项删除或顺序改变时,如何让剩余项平滑地移动到 new 位置?这是检验动画库能力的试金石。
framer-motion 拥有业界领先的 layout 属性。你只需加上这个 prop,它会自动测量元素前后的位置差异,并生成 FLIP(First, Last, Invert, Play)动画。无需计算任何坐标。
// framer-motion: 一键布局动画
import { motion, AnimatePresence } from "framer-motion";
function List({ items }) {
return (
<AnimatePresence>
{items.map((item) => (
<motion.div
key={item.id}
layout // 自动处理位置变化动画
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{item.text}
</motion.div>
))}
</AnimatePresence>
);
}
react-spring 本身不直接提供自动布局测量。你需要结合 @react-spring/web 的插值器,通常还需要配合第三方库(如 react-flip-toolkit)或手动计算 ref 的位置差来实现类似效果,代码复杂度显著增加。
// react-spring: 需手动处理或配合其他库
// 这里仅展示基本的样式绑定,布局重排需额外逻辑
import { useSpring, animated } from "@react-spring/web";
function Item({ style }) {
const props = useSpring({ ...style });
return <animated.div style={props} />;
}
// 注意:要实现自动重排,开发者需自行计算坐标差并传入 style
popmotion 完全没有布局概念。你必须使用 getBoundingClientRect 手动获取元素位置,计算差值,然后编写一个 action 来驱动动画。这赋予了最大灵活性,但也带来了最大的工作量。
// popmotion: 完全手动计算布局
import { tween } from "popmotion";
function animateLayoutChange(prevRect, nextRect) {
const deltaX = prevRect.left - nextRect.left;
tween({
from: deltaX,
to: 0,
onUpdate: (value) => {
element.style.transform = `translateX(${value}px)`;
}
}).start();
}
react-motion 同样缺乏内置的布局动画支持,且由于其渲染机制,在处理高频的布局更新时性能表现不佳,容易导致卡顿。
现代应用离不开拖拽、滑动和点击反馈。
framer-motion 内置了强大的手势系统。drag、whileTap、whileHover 等 props 让复杂交互变得声明式且简单。它自动处理指针事件、边界检测和惯性滑动。
// framer-motion: 内置拖拽和点击反馈
import { motion } from "framer-motion";
function DraggableCard() {
return (
<motion.div
drag
dragConstraints={{ left: 0, right: 300 }}
whileTap={{ scale: 0.95 }}
whileHover={{ scale: 1.05 }}
/>
);
}
react-spring 提供了 useDrag 等辅助钩子(通常在 @use-gesture/react 包中,现已合并或紧密集成),可以很好地与弹簧动画结合。但它更侧重于将手势数据映射到弹簧值,而非提供一套完整的交互组件系统。
// react-spring: 结合 useDrag 钩子
import { useSpring, animated } from "@react-spring/web";
import { useDrag } from "@use-gesture/react";
function Draggable() {
const [{ x }, api] = useSpring(() => ({ x: 0 }));
const bind = useDrag(({ movement: [mx] }) => {
api.start({ x: mx });
});
return <animated.div {...bind()} style={{ x }} />;
}
popmotion 提供了 pointer 和 input 模块来监听原始事件。你需要自己编写逻辑来判断“拖拽开始”、“拖拽中”和“拖拽结束”,并手动触发物理动画。适合定制特殊的交互物理效果。
// popmotion: 监听原始指针事件
import { pointer, spring } from "popmotion";
const input = pointer(document);
input.start(({ x }) => {
// 手动将 x 坐标映射到弹簧逻辑
spring({ from: currentX, to: x }).start({ next: setValue });
});
react-motion 没有内置手势支持,所有交互逻辑需完全手写,这在今天看来效率极低。
framer-motion 在内部做了大量优化,对于纯样式动画(如 transform, opacity),它会尝试绕过 React 的渲染周期,直接操作 DOM 或使用 GPU 加速,从而减少重渲染带来的开销。
react-spring 同样优秀,它返回的 animated 组件会拦截样式更新,直接修改 DOM 节点的内联样式,避免了触发 React 组件的重新渲染(Re-render),非常适合高帧率动画。
popmotion 性能取决于你的实现方式。由于它是手动的,如果你错误地在每一帧都触发 React 的 setState,会导致严重的性能瓶颈。正确使用它需要直接操作 DOM 引用。
react-motion 由于其设计年代较早,它倾向于在每一帧都触发 React 的状态更新和重渲染,这在复杂场景下会导致明显的性能下降,且不支持 React 18 的并发特性。
react-motion 的特别警示必须明确指出:react-motion 已经不再维护。其 GitHub 仓库已归档,npm 页面也标记了弃用信息。它不支持 React 的 Strict Mode,也不适配 Concurrent Features。在任何新项目中引入它都是技术债务。如果你的团队正在使用它,建议尽快制定迁移计划,转向 framer-motion(用于 UI 动效)或 react-spring(用于物理交互)。
| 特性 | framer-motion | react-spring | popmotion | react-motion |
|---|---|---|---|---|
| 核心哲学 | 声明式 UI 动画 | 物理弹簧模型 | 函数式动画原语 | 早期弹簧模型 (已弃用) |
| 上手难度 | ⭐ 低 (API 直观) | ⭐⭐ 中 (需理解物理) | ⭐⭐⭐ 高 (需手动编排) | ⭐ 低 (但已过时) |
| 布局动画 | ✅ 内置自动 (layout) | ❌ 需手动或第三方 | ❌ 完全手动 | ❌ 不支持 |
| 手势交互 | ✅ 内置 (drag, tap) | ⚠️ 需配合 use-gesture | ⚠️ 需手动监听事件 | ❌ 无支持 |
| React 集成 | 🏆 完美 (专用组件) | 🏆 优秀 (Hooks) | ⚠️ 无关 (纯 JS) | ❌ 过时 |
| 适用场景 | 绝大多数 UI 动画、原型 | 复杂交互、游戏化体验 | 自定义引擎、非 React 环境 | 无 (请迁移) |
最终建议:
对于 90% 的 React 项目,framer-motion 是最佳选择。它能让你用最少的时间实现最流畅的 UI 动效,特别是处理列表重排和手势时,它的表现无可替代。
如果你的应用需要高度定制的物理反馈(如复杂的仪表盘、模拟重力效果),或者你需要将动画值与复杂的数学逻辑深度绑定,react-spring 提供了更底层的控制力。
只有当你需要构建自己的动画库,或者在非 React 环境中需要轻量级的动画原语时,才考虑 popmotion。
请彻底遗忘 react-motion,拥抱现代工具链。
仅在你需要脱离 React 渲染周期直接控制动画,或者需要构建自定义的动画引擎、游戏逻辑时使用 popmotion。它是一个纯粹的函数式库,提供了最底层的 action、physics 和 input 原语,适合高级开发者进行底层架构设计,但不适合常规的 UI 开发。
如果你需要快速构建高质量的 UI 动画,特别是涉及列表重排(Layout Animations)、手势交互(拖拽、滑动)或简单的进入/退出效果,framer-motion 是首选。它的声明式 API 与 React 组件模型完美契合,能极大减少样板代码,且社区活跃,文档完善,适合绝大多数业务场景。
切勿在新项目中使用 react-motion。该库已正式弃用(Deprecated),不再接收功能更新或安全补丁。其 API 设计在现代 React 并发模式(Concurrent Mode)下存在局限性,且性能不如新一代库。现有项目应制定计划迁移至 framer-motion 或 react-spring。
当你需要处理复杂的交互逻辑,如跟随鼠标移动、物理碰撞效果、或者需要多个动画值之间进行紧密的数学关联时,选择 react-spring。它的弹簧物理模型能产生极其自然的运动曲线,且支持插值器(Interpolators)进行高性能的值转换,适合对动画流畅度和物理真实感有高要求的应用。