Framer Motion高级动画技巧与实战
手势动画进阶
1. 拖拽约束与惯性
tsx
export const DraggableCard = ({ children, onDragEnd }) => {
const constraintsRef = useRef(null);
return (
<motion.div
ref={constraintsRef}
style={{
width: '100%',
height: '400px',
background: '#f5f5f5',
borderRadius: '10px',
position: 'relative'
}}
>
<motion.div
drag
dragConstraints={constraintsRef}
dragElastic={0.2}
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
whileDrag={{ scale: 1.1 }}
onDragEnd={(e, info) => onDragEnd?.(info)}
style={{
width: '100px',
height: '100px',
background: '#1a73e8',
borderRadius: '10px',
cursor: 'grab'
}}
>
{children}
</motion.div>
</motion.div>
);
};2. 手势动画序列
tsx
export const GestureSequence = ({ onComplete }) => {
const controls = useAnimationControls();
const [step, setStep] = useState(1);
const handlePan = async (e, info) => {
if (info.offset.x > 100 && step === 1) {
await controls.start({ x: 100, transition: { type: "spring" } });
setStep(2);
} else if (info.offset.y > 100 && step === 2) {
await controls.start({ y: 100, transition: { type: "spring" } });
setStep(3);
onComplete?.();
}
};
return (
<motion.div
drag
dragConstraints={{ left: 0, right: 100, top: 0, bottom: 100 }}
onPan={handlePan}
animate={controls}
style={{
width: 100,
height: 100,
background: step === 3 ? '#34a853' : '#fbbc05',
borderRadius: 10
}}
>
步骤 {step}/3
</motion.div>
);
};高级动画效果
1. SVG路径动画
tsx
export const AnimatedLogo = ({ pathData, color = "#000", duration = 2 }) => {
return (
<motion.svg
width="100"
height="100"
viewBox="0 0 100 100"
initial="hidden"
animate="visible"
>
<motion.path
d={pathData}
stroke={color}
strokeWidth="2"
fill="none"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{
duration,
ease: "easeInOut",
repeat: Infinity,
repeatType: "reverse"
}}
/>
</motion.svg>
);
};2. 3D变换动画
tsx
export const Card3D = ({ children, depth = 50 }) => {
const [rotateX, setRotateX] = useState(0);
const [rotateY, setRotateY] = useState(0);
const handleMouseMove = (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
setRotateY((x - centerX) / centerX * 10);
setRotateX((centerY - y) / centerY * 10);
};
return (
<motion.div
style={{
perspective: 1000,
transformStyle: "preserve-3d"
}}
onMouseMove={handleMouseMove}
onMouseLeave={() => {
setRotateX(0);
setRotateY(0);
}}
>
<motion.div
animate={{
rotateX,
rotateY,
z: depth
}}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
style={{
width: "100%",
height: "100%",
backgroundColor: "white",
borderRadius: 20,
boxShadow: "0 5px 15px rgba(0,0,0,0.1)"
}}
>
{children}
</motion.div>
</motion.div>
);
};复杂动画编排
1. 动画序列管理器
tsx
export const SequenceManager = ({ sequences, onComplete }) => {
const controls = useAnimationControls();
useEffect(() => {
const runSequences = async () => {
for (const sequence of sequences) {
await controls.start({
...sequence.animation,
transition: { duration: sequence.duration }
});
}
onComplete?.();
};
runSequences();
}, [sequences]);
return (
<motion.div
animate={controls}
style={{
width: 100,
height: 100,
background: '#ea4335',
borderRadius: 10
}}
/>
);
};2. 视差滚动动画
tsx
export const ParallaxSection = ({ layers }) => {
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div style={{ height: "200vh", position: "relative" }}>
{layers.map(layer => (
<motion.div
key={layer.id}
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100vh"
}}
animate={{
y: scrollY * layer.speed
}}
transition={{
type: "spring",
stiffness: 100,
damping: 30,
restDelta: 0.001
}}
>
{layer.content}
</motion.div>
))}
</div>
);
};实战案例:交互式产品展示
tsx
export const ProductShowcase = ({ product }) => {
const [activeFeature, setActiveFeature] = useState(null);
const controls = useAnimationControls();
const showFeature = async (index) => {
setActiveFeature(index);
await controls.start({
scale: [1, 1.1, 1],
rotate: [0, 5, -5, 0],
transition: { duration: 0.5 }
});
};
return (
<div style={{ padding: 40 }}>
<motion.div
layoutId="product-image"
style={{
position: 'relative',
width: '100%',
maxWidth: 500,
margin: '0 auto'
}}
>
<motion.img
src={product.image}
alt={product.name}
animate={controls}
style={{
width: '100%',
height: 'auto',
borderRadius: 20
}}
/>
{product.features.map((feature, index) => (
<motion.div
key={index}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: index * 0.2 }}
onClick={() => showFeature(index)}
style={{
position: 'absolute',
width: 30,
height: 30,
borderRadius: '50%',
background: activeFeature === index ? '#1a73e8' : '#fff',
border: '2px solid #1a73e8',
cursor: 'pointer',
// 根据需要调整位置
top: `${20 + index * 20}%`,
left: `${20 + index * 20}%`
}}
/>
))}
</motion.div>
<AnimatePresence mode="wait">
{activeFeature !== null && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
style={{
marginTop: 20,
padding: 20,
background: '#f8f9fa',
borderRadius: 10
}}
>
{product.features[activeFeature]}
</motion.div>
)}
</AnimatePresence>
</div>
);
};小结
本文介绍了Framer Motion的高级动画技巧:
- 复杂手势动画
- SVG和3D动画
- 动画序列管理
- 视差滚动效果
- 实战案例展示