Framer Motion 动画不损害性能:模式与陷阱
发布: (2026年4月7日 GMT+8 16:55)
3 分钟阅读
原文: Dev.to
Source: Dev.to
黄金法则:仅对 Transform 和 Opacity 进行动画
// Bad — triggers layout recalculation (expensive)
// Good — GPU‑composited, no layout (cheap)会触发布局的属性: width, height, top, left, padding, margin。
不会触发布局的属性: x, y, scale, rotate, opacity, skew。
进入动画
const fadeUp = {
hidden: { opacity: 0, y: 24 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: 'easeOut' },
},
};
function Card({ children }: { children: React.ReactNode }) {
return (
{children}
);
}viewport={{ once: true }} 非常关键——如果没有它,元素每次进入视口时都会重新触发动画。
子元素错位(Staggered Children)
const container = {
hidden: {},
visible: {
transition: { staggerChildren: 0.1, delayChildren: 0.2 },
},
};
const item = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.3 } },
};
function ProductGrid({ products }: { products: Product[] }) {
return (
{products.map(product => (
))}
);
}悬停和点击状态
function Button({ children, onClick }: ButtonProps) {
return (
{children}
);
}弹簧物理效果比线性过渡在交互元素上更自然。
布局动画
function ExpandableCard({ title, content }: CardProps) {
const [expanded, setExpanded] = useState(false);
return (
setExpanded(!expanded)}>
{title}
{expanded && (
{content}
)}
);
}layout 属性会自动为尺寸变化添加动画——无需显式地对宽度/高度进行动画处理。
使用 AnimatePresence 实现退出动画
import { AnimatePresence, motion } from 'framer-motion';
function Notifications({ notifications }: { notifications: Notification[] }) {
return (
{notifications.map(notification => (
{notification.message}
))}
);
}如果不使用 AnimatePresence,元素会直接消失——没有退出动画。
性能技巧
// 1. Use style prop for non‑animating styles (skips motion overhead)
// 2. Disable animations for users who prefer reduced motion
import { useReducedMotion } from 'framer-motion';
function AnimatedCard() {
const shouldReduce = useReducedMotion();
return (
);
}
// 3. Avoid animating hundreds of elements simultaneously
// Cap stagger at ~20 items, virtualize the restAI SaaS Starter Kit 已预置 Framer Motion 动画在登录页和仪表盘上——全部使用 GPU 合成属性。一次性 $99。