Framer Motion性能优化与最佳实践
性能优化策略
1. 使用useReducedMotion适配用户偏好
某些用户可能在系统设置中开启了减少动画选项,我们应该尊重这个设置:
tsx
import { useReducedMotion } from "framer-motion"
function AdaptiveAnimation() {
const shouldReduceMotion = useReducedMotion()
const variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
x: shouldReduceMotion ? 0 : 100,
transition: {
duration: shouldReduceMotion ? 0.1 : 0.5
}
}
}
return (
<motion.div
variants={variants}
initial="hidden"
animate="visible"
>
适应性动画
</motion.div>
)
}2. 使用useMemo优化动画配置
对于复杂的动画配置,应该使用useMemo避免不必要的重新计算:
tsx
function OptimizedAnimation() {
const [isHovered, setIsHovered] = useState(false);
const variants = useMemo(() => ({
idle: {
scale: 1,
boxShadow: "0px 0px 0px rgba(0,0,0,0.2)"
},
hover: {
scale: 1.05,
boxShadow: "0px 5px 15px rgba(0,0,0,0.2)"
}
}), []); // 配置不依赖于任何状态,只需计算一次
return (
<motion.div
variants={variants}
animate={isHovered ? "hover" : "idle"}
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
>
优化后的动画
</motion.div>
)
}3. 使用layout属性时的性能优化
tsx
function OptimizedLayout() {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div style={{ padding: 20 }}>
<motion.div
layout
// 使用layoutId可以避免不必要的布局计算
layoutId="expandable-card"
// 使用style而不是animate来设置非动画属性
style={{
width: isExpanded ? 300 : 150,
height: isExpanded ? 300 : 150,
background: '#ea4335',
}}
// 只对真正需要动画的属性使用transition
transition={{
layout: {
duration: 0.3,
ease: "easeInOut"
}
}}
onClick={() => setIsExpanded(!isExpanded)}
/>
</div>
)
}动画编排
1. 使用useAnimation控制复杂动画序列
tsx
function SequenceAnimation() {
const controls = useAnimation();
async function sequence() {
await controls.start({ scale: 1.5 });
await controls.start({ rotate: 180 });
await controls.start({ scale: 1 });
return await controls.start({ rotate: 0 });
}
return (
<div>
<motion.div
animate={controls}
initial={{ scale: 1, rotate: 0 }}
style={{
width: 100,
height: 100,
background: '#4285f4',
borderRadius: 10
}}
/>
<button onClick={sequence}>
播放动画序列
</button>
</div>
)
}2. 使用useAnimationControls实现更细粒度的控制
tsx
function ControlledAnimation() {
const controls = useAnimationControls();
const [status, setStatus] = useState('idle');
const startAnimation = async () => {
setStatus('animating');
await controls.start({
scale: [1, 2, 2, 1],
rotate: [0, 0, 180, 180],
borderRadius: ["10%", "50%", "50%", "10%"],
});
setStatus('completed');
};
return (
<div>
<motion.div
animate={controls}
initial={{ scale: 1 }}
style={{
width: 100,
height: 100,
background: '#fbbc05',
}}
/>
<button
onClick={startAnimation}
disabled={status === 'animating'}
>
{status === 'animating' ? '动画中...' : '开始动画'}
</button>
</div>
)
}常见陷阱与解决方案
1. 避免动画过程中的布局抖动
tsx
function StableAnimation() {
return (
<motion.div
style={{
// 使用transform而不是width/height来实现缩放
width: 100,
height: 100,
background: '#34a853',
}}
whileHover={{
scale: 1.1, // 使用scale而不是width/height
// 避免使用margin/padding的动画
}}
/>
)
}2. 处理动画中的内容溢出
tsx
function OverflowHandling() {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div style={{ overflow: 'hidden' }}> {/* 父容器处理溢出 */}
<motion.div
initial={false}
animate={{
height: isExpanded ? 'auto' : 0
}}
transition={{
duration: 0.3,
ease: "easeInOut"
}}
>
<div style={{ padding: 20 }}>
展开/收起的内容
</div>
</motion.div>
</div>
)
}实战案例:优化的图片加载动画
tsx
function OptimizedImageLoader() {
const [isLoaded, setIsLoaded] = useState(false);
const controls = useAnimationControls();
const handleLoad = async () => {
setIsLoaded(true);
// 使用序列动画使加载效果更流畅
await controls.start({ opacity: 1 });
await controls.start({ scale: 1 });
};
return (
<div style={{ overflow: 'hidden' }}>
<motion.img
src="large-image.jpg"
initial={{ opacity: 0, scale: 0.8 }}
animate={controls}
onLoad={handleLoad}
style={{
width: '100%',
height: 'auto',
willChange: 'transform', // 优化性能
}}
transition={{
opacity: { duration: 0.3 },
scale: { duration: 0.3, delay: 0.1 }
}}
/>
{!isLoaded && (
<motion.div
initial={{ opacity: 0.5 }}
animate={{ opacity: 1 }}
transition={{
repeat: Infinity,
duration: 1,
ease: "easeInOut"
}}
>
加载中...
</motion.div>
)}
</div>
)
}调试技巧
1. 使用开发者工具
tsx
import { motion, MotionConfig } from "framer-motion"
function DebugComponent() {
return (
<MotionConfig reducedMotion="user">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
// 开发环境下显示动画信息
whileHover={{ scale: 1.1 }}
/>
</MotionConfig>
)
}小结
本文介绍了Framer Motion的性能优化策略和最佳实践:
- 性能优化技巧
- 动画编排方法
- 常见问题解决方案
- 调试技巧