Mastering Animations in React with Framer Motion: A Complete Guide


Mastering Animations in React with Framer Motion
Modern web applications require smooth, engaging animations to create delightful user experiences. Framer Motion has emerged as the go-to animation library for React, offering a declarative API that makes complex animations simple to implement while maintaining excellent performance.
Why Framer Motion?
Framer Motion stands out from other animation libraries for several compelling reasons:
- Declarative API: Define animations using simple props rather than imperative code
- Performance: Hardware-accelerated animations that run smoothly at 60fps
- Gesture Support: Built-in support for drag, hover, tap, and complex gestures
- Layout Animations: Automatic animations when layout changes occur
- Server-Side Rendering: Full SSR support for Next.js and other frameworks
- TypeScript Support: Excellent TypeScript definitions for type safety
Together with React’s component-based architecture, Framer Motion enables developers to create sophisticated animations with minimal code complexity.
Getting Started with Framer Motion
Installation
First, let’s install Framer Motion in your React project:
# Using npm
npm install framer-motion
# Using yarn
yarn add framer-motion
# Using pnpm
pnpm add framer-motion
Basic Setup
Framer Motion works by replacing standard HTML elements with motion components. Here’s a simple example:
import { motion } from 'framer-motion';
function App() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<h1>Hello, Framer Motion!</h1>
</motion.div>
);
}
Core Animation Concepts
Basic Animations with Initial and Animate
The foundation of Framer Motion animations lies in the initial
and animate
props:
import { motion } from 'framer-motion';
const FadeInComponent = () => {
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="p-8 bg-blue-500 text-white rounded-lg"
>
<h2>I fade in and slide up!</h2>
</motion.div>
);
};
Keyframe Animations
For more complex animations, you can use keyframes:
const BouncingBall = () => {
return (
<motion.div
className="w-16 h-16 bg-red-500 rounded-full"
animate={{
y: [0, -100, 0],
scale: [1, 1.2, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
);
};
Stagger Animations
Create beautiful staggered animations for lists:
import { motion } from 'framer-motion';
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const StaggeredList = ({ items }) => {
return (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
className="space-y-4"
>
{items.map((item, index) => (
<motion.li
key={index}
variants={itemVariants}
className="p-4 bg-gray-100 rounded-lg"
>
{item}
</motion.li>
))}
</motion.ul>
);
};
Advanced Animation Techniques
Gesture-Based Animations
Framer Motion excels at gesture-based interactions:
const InteractiveCard = () => {
return (
<motion.div
className="w-64 h-64 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg cursor-pointer"
whileHover={{
scale: 1.05,
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)"
}}
whileTap={{ scale: 0.95 }}
drag
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
dragElastic={0.1}
>
<div className="p-6 text-white">
<h3 className="text-xl font-bold">Interactive Card</h3>
<p>Hover, click, and drag me!</p>
</div>
</motion.div>
);
};
Layout Animations
One of Framer Motion’s most powerful features is automatic layout animations:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
const ExpandableCard = () => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<motion.div
layout
onClick={() => setIsExpanded(!isExpanded)}
className="bg-white p-6 rounded-lg shadow-lg cursor-pointer"
style={{ maxWidth: isExpanded ? '400px' : '200px' }}
>
<motion.h3 layout="position" className="text-xl font-bold mb-4">
Expandable Content
</motion.h3>
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
>
<p>This content appears when the card expands!</p>
<p>Layout animations handle the smooth transition automatically.</p>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
};
Custom Hooks for Reusable Animations
Create custom hooks to reuse animation logic:
import { useAnimation } from 'framer-motion';
import { useInView } from 'framer-motion';
import { useRef, useEffect } from 'react';
// Custom hook for scroll-triggered animations
const useScrollAnimation = () => {
const controls = useAnimation();
const ref = useRef(null);
const inView = useInView(ref, { once: true, threshold: 0.1 });
useEffect(() => {
if (inView) {
controls.start('visible');
}
}, [controls, inView]);
return { ref, controls };
};
// Usage
const ScrollAnimatedComponent = () => {
const { ref, controls } = useScrollAnimation();
return (
<motion.div
ref={ref}
initial="hidden"
animate={controls}
variants={{
hidden: { opacity: 0, y: 50 },
visible: { opacity: 1, y: 0, transition: { duration: 0.6 } }
}}
className="p-8 bg-green-500 text-white rounded-lg"
>
<h2>I animate when scrolled into view!</h2>
</motion.div>
);
};
Building Complex UI Components
Animated Modal
Create a sophisticated modal with backdrop blur and smooth transitions:
import { motion, AnimatePresence } from 'framer-motion';
const modalVariants = {
hidden: {
opacity: 0,
scale: 0.8,
y: 50
},
visible: {
opacity: 1,
scale: 1,
y: 0,
transition: {
type: "spring",
damping: 25,
stiffness: 300
}
},
exit: {
opacity: 0,
scale: 0.8,
y: 50,
transition: {
duration: 0.2
}
}
};
const backdropVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
exit: { opacity: 0 }
};
const AnimatedModal = ({ isOpen, onClose, children }) => {
return (
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center"
variants={backdropVariants}
initial="hidden"
animate="visible"
exit="exit"
>
{/* Backdrop */}
<motion.div
className="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<motion.div
className="relative bg-white p-8 rounded-xl shadow-2xl max-w-md w-full mx-4"
variants={modalVariants}
onClick={(e) => e.stopPropagation()}
>
<button
onClick={onClose}
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700"
>
✕
</button>
{children}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
};
Animated Navigation Menu
Build a sliding navigation menu with smooth transitions:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
const menuVariants = {
closed: {
x: '-100%',
transition: {
type: 'spring',
stiffness: 400,
damping: 40
}
},
open: {
x: 0,
transition: {
type: 'spring',
stiffness: 400,
damping: 40
}
}
};
const menuItemVariants = {
closed: { opacity: 0, x: -20 },
open: { opacity: 1, x: 0 }
};
const AnimatedNavigation = () => {
const [isOpen, setIsOpen] = useState(false);
const menuItems = [
'Home', 'About', 'Services', 'Portfolio', 'Contact'
];
return (
<>
{/* Menu Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="fixed top-4 left-4 z-50 p-2 bg-blue-500 text-white rounded-md"
>
{isOpen ? '✕' : '☰'}
</button>
{/* Overlay */}
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setIsOpen(false)}
/>
)}
</AnimatePresence>
{/* Menu */}
<motion.nav
className="fixed top-0 left-0 h-full w-80 bg-white shadow-2xl z-50"
variants={menuVariants}
initial="closed"
animate={isOpen ? 'open' : 'closed'}
>
<div className="p-8 pt-20">
<motion.ul className="space-y-6">
{menuItems.map((item, index) => (
<motion.li
key={item}
variants={menuItemVariants}
transition={{ delay: index * 0.1 }}
>
<a
href="#"
className="block text-xl font-semibold text-gray-800 hover:text-blue-500 transition-colors"
>
{item}
</a>
</motion.li>
))}
</motion.ul>
</div>
</motion.nav>
</>
);
};
Performance Optimization
Transform vs Layout Properties
For optimal performance, prefer transform properties over layout properties:
// ✅ Good - Uses transform (GPU accelerated)
<motion.div
animate={{ x: 100, scale: 1.2, rotate: 45 }}
/>
// ❌ Avoid - Triggers layout recalculation
<motion.div
animate={{ left: 100, width: 200, height: 150 }}
/>
Using will-change CSS Property
For complex animations, use the will-change
CSS property:
const OptimizedComponent = () => {
return (
<motion.div
style={{ willChange: 'transform' }}
animate={{ rotate: 360 }}
transition={{ repeat: Infinity, duration: 2 }}
className="w-20 h-20 bg-blue-500"
/>
);
};
Reducing Re-renders
Use variants to prevent unnecessary re-renders:
const cardVariants = {
idle: { scale: 1 },
hover: { scale: 1.05 },
tap: { scale: 0.95 }
};
const OptimizedCard = ({ children }) => {
return (
<motion.div
variants={cardVariants}
initial="idle"
whileHover="hover"
whileTap="tap"
className="p-6 bg-white rounded-lg shadow-lg"
>
{children}
</motion.div>
);
};
Integration with React Ecosystem
Using with React Router
Animate route transitions with Framer Motion and React Router:
import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence, 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
};
const AnimatedRoutes = () => {
const location = useLocation();
return (
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route
path="/"
element={
<motion.div
initial="initial"
animate="in"
exit="out"
variants={pageVariants}
transition={pageTransition}
>
<HomePage />
</motion.div>
}
/>
<Route
path="/about"
element={
<motion.div
initial="initial"
animate="in"
exit="out"
variants={pageVariants}
transition={pageTransition}
>
<AboutPage />
</motion.div>
}
/>
</Routes>
</AnimatePresence>
);
};
Integration with State Management
Combine Framer Motion with Redux or Zustand for complex state-driven animations:
import { useSelector } from 'react-redux';
import { motion } from 'framer-motion';
const NotificationBanner = () => {
const notifications = useSelector(state => state.notifications);
return (
<AnimatePresence>
{notifications.map(notification => (
<motion.div
key={notification.id}
initial={{ opacity: 0, y: -50, scale: 0.8 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -50, scale: 0.8 }}
layout
className="p-4 mb-2 bg-blue-500 text-white rounded-lg"
>
{notification.message}
</motion.div>
))}
</AnimatePresence>
);
};
Best Practices and Tips
Animation Accessibility
Always consider accessibility when implementing animations:
import { useReducedMotion } from 'framer-motion';
const AccessibleAnimation = () => {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
animate={{ x: shouldReduceMotion ? 0 : 100 }}
transition={{ duration: shouldReduceMotion ? 0 : 0.5 }}
>
Respects user motion preferences
</motion.div>
);
};
Testing Animated Components
Use testing utilities that work with Framer Motion:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AnimatedButton } from './AnimatedButton';
test('animated button responds to interactions', async () => {
const user = userEvent.setup();
render(<AnimatedButton>Click me</AnimatedButton>);
const button = screen.getByRole('button');
// Test hover state
await user.hover(button);
expect(button).toHaveStyle('transform: scale(1.05)');
// Test click state
await user.click(button);
expect(button).toHaveStyle('transform: scale(0.95)');
});
Conclusion
Framer Motion transforms how we approach animations in React applications, making complex interactions achievable with minimal code. The library’s declarative API, excellent performance, and comprehensive feature set make it an essential tool for modern React development.
Key takeaways for mastering Framer Motion include understanding the core concepts of initial, animate, and variants, leveraging gesture-based interactions for enhanced user experience, utilizing layout animations for seamless UI transitions, optimizing performance by preferring transforms over layout properties, and considering accessibility in all animation implementations.
Whether you’re building simple micro-interactions or complex animated interfaces, Framer Motion provides the tools needed to create engaging, performant animations that delight users. As you continue to explore the library, remember that great animations should feel natural, serve a purpose, and enhance the overall user experience rather than distract from it.
The combination of React’s component architecture and Framer Motion’s animation capabilities opens up endless possibilities for creating memorable web experiences that stand out in today’s competitive digital landscape.

About Timothy Benjamin
A Freelance Full-Stack Developer who brings company website visions to reality.