Using Framer Motion for Complex React Animations
Introduction
Framer Motion brings powerful animation capabilities to React with declarative APIs. This guide covers advanced animation techniques for modern web applications.
Prerequisites
- React project setup
- Basic understanding of CSS animations
Step 1: Install Framer Motion
npm install framer-motion
Step 2: Basic Motion Components
Create components/BasicAnimations.tsx
:
import { motion } from 'framer-motion';
export default function BasicAnimations() {
return (
<div className="p-8 space-y-8">
{/* Simple fade in */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="bg-blue-500 text-white p-4 rounded"
>
Fade In Animation
</motion.div>
{/* Slide and scale */}
<motion.div
initial={{ x: -100, scale: 0.8 }}
animate={{ x: 0, scale: 1 }}
transition={{ type: "spring", stiffness: 100 }}
className="bg-green-500 text-white p-4 rounded"
>
Slide & Scale Animation
</motion.div>
{/* Rotating card */}
<motion.div
whileHover={{ rotate: 180, scale: 1.1 }}
whileTap={{ scale: 0.9 }}
transition={{ type: "spring", stiffness: 300 }}
className="bg-purple-500 text-white p-4 rounded cursor-pointer"
>
Hover & Tap Animation
</motion.div>
</div>
);
}
Step 3: Layout Animations
Create components/LayoutAnimations.tsx
:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
export default function LayoutAnimations() {
const [isExpanded, setIsExpanded] = useState(false);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
const removeItem = (id: number) => {
setItems(items.filter(item => item !== id));
};
return (
<div className="p-8 space-y-8">
{/* Expandable card */}
<motion.div
layout
className="bg-white border rounded-lg shadow-md cursor-pointer overflow-hidden"
onClick={() => setIsExpanded(!isExpanded)}
>
<motion.div layout className="p-6">
<motion.h3 layout className="text-xl font-semibold mb-2">
Expandable Card
</motion.h3>
<motion.p layout className="text-gray-600">
Click to expand or collapse
</motion.p>
</motion.div>
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="px-6 pb-6"
>
<p className="text-gray-700">
This is additional content that appears when expanded.
The layout animation handles the smooth transition.
</p>
</motion.div>
)}
</AnimatePresence>
</motion.div>
{/* Animated list */}
<div>
<h3 className="text-xl font-semibold mb-4">Animated List</h3>
<motion.div layout className="space-y-2">
<AnimatePresence>
{items.map(item => (
<motion.div
key={item}
layout
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, x: -100 }}
whileHover={{ scale: 1.02, x: 10 }}
className="bg-gray-100 p-4 rounded cursor-pointer flex justify-between items-center"
onClick={() => removeItem(item)}
>
<span>Item {item}</span>
<span className="text-red-500 text-sm">Click to remove</span>
</motion.div>
))}
</AnimatePresence>
</motion.div>
</div>
</div>
);
}
Step 4: Gesture Animations
Create components/GestureAnimations.tsx
:
import { motion, useMotionValue, useTransform } from 'framer-motion';
export default function GestureAnimations() {
const x = useMotionValue(0);
const rotate = useTransform(x, [-100, 100], [-45, 45]);
const opacity = useTransform(x, [-100, -50, 0, 50, 100], [0, 1, 1, 1, 0]);
return (
<div className="p-8 space-y-8">
<h2 className="text-2xl font-bold">Gesture Animations</h2>
{/* Draggable card */}
<div className="h-64 bg-gray-100 rounded-lg relative overflow-hidden">
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
dragElastic={0.2}
whileDrag={{ scale: 1.1, rotate: 5 }}
className="absolute top-1/2 left-1/2 w-24 h-32 bg-blue-500 rounded-lg shadow-lg cursor-grab active:cursor-grabbing flex items-center justify-center text-white font-semibold"
style={{ x: "-50%", y: "-50%" }}
>
Drag Me
</motion.div>
</div>
{/* Swipeable card */}
<motion.div
style={{ x, rotate, opacity }}
drag="x"
dragConstraints={{ left: 0, right: 0 }}
dragElastic={1}
onDragEnd={(event, info) => {
if (info.offset.x > 100) {
// Swiped right
console.log('Swiped right!');
} else if (info.offset.x < -100) {
// Swiped left
console.log('Swiped left!');
}
}}
className="w-80 h-48 bg-gradient-to-br from-pink-400 to-red-500 rounded-xl shadow-lg cursor-grab active:cursor-grabbing flex items-center justify-center text-white text-xl font-semibold mx-auto"
>
Swipe Left or Right
</motion.div>
</div>
);
}
Step 5: Complex Page Transitions
Create components/PageTransitions.tsx
:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
const pages = [
{ id: 1, title: 'Home', color: 'bg-blue-500' },
{ id: 2, title: 'About', color: 'bg-green-500' },
{ id: 3, title: 'Contact', color: 'bg-purple-500' },
];
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
};
export default function PageTransitions() {
const [currentPage, setCurrentPage] = useState(0);
return (
<div className="h-96 relative overflow-hidden rounded-lg">
{/* Navigation */}
<div className="absolute top-4 left-4 z-20 space-x-2">
{pages.map((page, index) => (
<button
key={page.id}
onClick={() => setCurrentPage(index)}
className={`px-4 py-2 rounded ${
currentPage === index
? 'bg-white text-gray-800'
: 'bg-white bg-opacity-20 text-white'
}`}
>
{page.title}
</button>
))}
</div>
{/* Page content */}
<AnimatePresence mode="wait">
<motion.div
key={currentPage}
initial="initial"
animate="in"
exit="out"
variants={pageVariants}
transition={pageTransition}
className={`absolute inset-0 flex items-center justify-center text-white text-4xl font-bold ${pages[currentPage].color}`}
>
{pages[currentPage].title} Page
</motion.div>
</AnimatePresence>
</div>
);
}
Step 6: Staggered Animations
Create components/StaggeredAnimations.tsx
:
import { motion } from 'framer-motion';
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 }
};
export default function StaggeredAnimations() {
const items = Array.from({ length: 12 }, (_, i) => i + 1);
return (
<div className="p-8">
<h2 className="text-2xl font-bold mb-8">Staggered Grid Animation</h2>
<motion.div
variants={container}
initial="hidden"
animate="show"
className="grid grid-cols-4 gap-4"
>
{items.map(num => (
<motion.div
key={num}
variants={item}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="aspect-square bg-gradient-to-br from-blue-400 to-purple-500 rounded-lg flex items-center justify-center text-white font-bold text-2xl cursor-pointer"
>
{num}
</motion.div>
))}
</motion.div>
</div>
);
}
Step 7: Main Demo Component
import BasicAnimations from '@/components/BasicAnimations';
import LayoutAnimations from '@/components/LayoutAnimations';
import GestureAnimations from '@/components/GestureAnimations';
import PageTransitions from '@/components/PageTransitions';
import StaggeredAnimations from '@/components/StaggeredAnimations';
export default function Home() {
return (
<main className="min-h-screen bg-gray-50">
<div className="container mx-auto py-12">
<h1 className="text-4xl font-bold text-center mb-12">
Framer Motion Animations
</h1>
<div className="space-y-16">
<BasicAnimations />
<LayoutAnimations />
<GestureAnimations />
<PageTransitions />
<StaggeredAnimations />
</div>
</div>
</main>
);
}
Summary
Framer Motion provides declarative animation APIs with layout animations, gestures, and complex transitions. Use motion
components, AnimatePresence
, and variant patterns for production-ready animations.