Framer Motion企业级实践指南
动画系统设计
1. 动画主题配置
创建统一的动画配置系统,确保整个应用的动画风格一致:
tsx
// src/theme/animations.ts
export const animationTokens = {
duration: {
fast: 0.2,
normal: 0.3,
slow: 0.5
},
ease: {
default: [0.4, 0, 0.2, 1],
in: [0.4, 0, 1, 1],
out: [0, 0, 0.2, 1],
inOut: [0.4, 0, 0.2, 1]
},
transition: {
default: {
duration: 0.3,
ease: [0.4, 0, 0.2, 1]
},
spring: {
type: "spring",
stiffness: 300,
damping: 30
}
}
} as const;
// 动画变体预设
export const animationPresets = {
fadeIn: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
transition: animationTokens.transition.default
},
slideInRight: {
initial: { x: 20, opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: -20, opacity: 0 },
transition: animationTokens.transition.default
}
} as const;2. 动画Provider组件
tsx
// src/components/AnimationProvider.tsx
interface AnimationProviderProps {
children: React.ReactNode;
reducedMotion?: boolean;
}
export const AnimationProvider: React.FC<AnimationProviderProps> = ({
children,
reducedMotion = false
}) => {
return (
<MotionConfig
reducedMotion={reducedMotion ? "always" : "never"}
transition={animationTokens.transition.default}
>
{children}
</MotionConfig>
);
};企业级组件实现
1. 数据表格动画
tsx
interface AnimatedTableProps<T> {
data: T[];
columns: {
key: keyof T;
title: string;
render?: (value: T[keyof T], record: T) => React.ReactNode;
}[];
loading?: boolean;
onRowClick?: (record: T) => void;
}
export function AnimatedTable<T extends { id: string | number }>({
data,
columns,
loading,
onRowClick
}: AnimatedTableProps<T>) {
return (
<div style={{ overflow: 'hidden' }}>
<motion.table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
{columns.map(column => (
<th key={String(column.key)} style={{ padding: '12px' }}>
{column.title}
</th>
))}
</tr>
</thead>
<AnimatePresence mode="wait">
{loading ? (
<motion.tbody
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<tr>
<td colSpan={columns.length} style={{ textAlign: 'center', padding: '20px' }}>
加载中...
</td>
</tr>
</motion.tbody>
) : (
<motion.tbody>
<AnimatedList
items={data}
keyExtractor={item => String(item.id)}
staggerDelay={0.05}
renderItem={(record) => (
<motion.tr
whileHover={{ backgroundColor: '#f5f5f5' }}
onClick={() => onRowClick?.(record)}
style={{ cursor: onRowClick ? 'pointer' : 'default' }}
>
{columns.map(column => (
<td key={String(column.key)} style={{ padding: '12px' }}>
{column.render
? column.render(record[column.key], record)
: String(record[column.key])}
</td>
))}
</motion.tr>
)}
/>
</motion.tbody>
)}
</AnimatePresence>
</motion.table>
</div>
);
}2. 数据可视化动画
tsx
interface AnimatedChartProps {
data: { value: number; label: string }[];
height?: number;
color?: string;
}
export const AnimatedChart: React.FC<AnimatedChartProps> = ({
data,
height = 200,
color = '#1a73e8'
}) => {
const maxValue = Math.max(...data.map(d => d.value));
return (
<div style={{ height, display: 'flex', alignItems: 'flex-end', gap: 8 }}>
<AnimatePresence>
{data.map((item, index) => (
<motion.div
key={item.label}
initial={{ height: 0 }}
animate={{ height: `${(item.value / maxValue) * 100}%` }}
exit={{ height: 0 }}
transition={{
duration: 0.5,
delay: index * 0.1,
ease: "easeOut"
}}
style={{
flex: 1,
backgroundColor: color,
borderRadius: '4px 4px 0 0',
position: 'relative'
}}
>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{
position: 'absolute',
top: -24,
left: '50%',
transform: 'translateX(-50%)',
fontSize: 12
}}
>
{item.value}
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{
position: 'absolute',
bottom: -24,
left: '50%',
transform: 'translateX(-50%)',
fontSize: 12
}}
>
{item.label}
</motion.div>
</motion.div>
))}
</AnimatePresence>
</div>
);
};3. 路由转场动画
- 使用BrowserRouter和Routes:在react-router-dom v6中,使用BrowserRouter和Routes来定义路由。
- 使用AnimatePresence:AnimatePresence用于管理组件的进入和退出动画。使用mode="wait"来确保退出动画完成后再开始进入动画。
- 定义动画:使用motion.div来定义页面的动画效果。
jsx
// src/App.jsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import Home from './Home';
import About from './About';
function App() {
return (
<Router>
<AnimatePresence mode="wait">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</AnimatePresence>
</Router>
);
}
export default App;
// src/Home.jsx
import React from 'react';
import { motion } from 'framer-motion';
const pageVariants = {
initial: {
opacity: 0,
x: "-100vw"
},
in: {
opacity: 1,
x: 0
},
out: {
opacity: 0,
x: "100vw"
}
};
const pageTransition = {
type: "tween",
ease: "anticipate",
duration: 0.5
};
function Home() {
return (
<motion.div
initial="initial"
animate="in"
exit="out"
variants={pageVariants}
transition={pageTransition}
>
<h1>Home Page</h1>
</motion.div>
);
}
export default Home;
// src/About.jsx
import React from 'react';
import { motion } from 'framer-motion';
const pageVariants = {
initial: {
opacity: 0,
x: "-100vw"
},
in: {
opacity: 1,
x: 0
},
out: {
opacity: 0,
x: "100vw"
}
};
const pageTransition = {
type: "tween",
ease: "anticipate",
duration: 0.5
};
function About() {
return (
<motion.div
initial="initial"
animate="in"
exit="out"
variants={pageVariants}
transition={pageTransition}
>
<h1>About Page</h1>
</motion.div>
);
}
export default About;性能监控与优化
1. 性能监控HOC
tsx
// src/hocs/withAnimationMetrics.tsx
export function withAnimationMetrics<P extends object>(
WrappedComponent: React.ComponentType<P>,
componentName: string
) {
return function WithAnimationMetrics(props: P) {
useEffect(() => {
const startTime = performance.now();
return () => {
const duration = performance.now() - startTime;
console.log(`[Animation Metrics] ${componentName} rendered in ${duration}ms`);
// 可以发送到性能监控系统
if (duration > 16) { // 超过一帧的时间
console.warn(`[Animation Performance Warning] ${componentName} animation took too long`);
}
};
}, []);
return <WrappedComponent {...props} />;
};
}2. 自定义动画Hook
tsx
// src/hooks/useAnimationControl.ts
interface AnimationControl {
start: () => Promise<void>;
stop: () => void;
reset: () => void;
isAnimating: boolean;
}
export function useAnimationControl(
controls: AnimationControls,
animation: TargetAndTransition
): AnimationControl {
const [isAnimating, setIsAnimating] = useState(false);
const start = async () => {
setIsAnimating(true);
await controls.start(animation);
setIsAnimating(false);
};
const stop = () => {
controls.stop();
setIsAnimating(false);
};
const reset = () => {
controls.set(animation);
setIsAnimating(false);
};
return {
start,
stop,
reset,
isAnimating
};
}实战案例:企业级数据大屏
tsx
interface DataScreenProps {
data: {
charts: { id: string; data: any[] }[];
tables: { id: string; data: any[] }[];
};
}
export const DataScreen: React.FC<DataScreenProps> = ({ data }) => {
return (
<div style={{ padding: 24 }}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: 24
}}
>
{data.charts.map((chart, index) => (
<motion.div
key={chart.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
>
<AnimatedChart data={chart.data} />
</motion.div>
))}
{data.tables.map((table, index) => (
<motion.div
key={table.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
>
<AnimatedTable data={table.data} />
</motion.div>
))}
</motion.div>
</div>
);
};小结
本文介绍了Framer Motion在企业级应用中的实践:
- 动画系统设计
- 企业级组件实现
- 性能监控与优化
- 实战案例展示