React Framer Motion
Introduction
Animations can transform a static React application into an engaging, interactive experience. While CSS animations and transitions are powerful, they can become complex when building advanced interactions. This is where Framer Motion comes in - a production-ready motion library for React that makes creating animations simple and intuitive.
Framer Motion provides a declarative API that lets you animate React components with minimal code while handling complex animation challenges like interruptions, gesture recognition, and responsive animations.
In this tutorial, we'll explore how to use Framer Motion to add beautiful animations to your React applications.
Getting Started with Framer Motion
Installation
First, you need to install Framer Motion in your React project:
npm install framer-motion
# or
yarn add framer-motion
Basic Concepts
Framer Motion is built around a few core components and concepts:
motion
components: These are enhanced HTML/SVG elements with animation capabilities- Variants: Predefined animation states that can be reused
- Gestures: Built-in support for hover, tap, drag, and other interactions
- AnimatePresence: For animating components when they're removed from the DOM
Let's start with the most basic example:
Your First Animation
The simplest way to use Framer Motion is with the motion
component. It works as a drop-in replacement for HTML elements:
import { motion } from 'framer-motion';
function SimpleBox() {
return (
<motion.div
style={{
width: 100,
height: 100,
backgroundColor: 'blue',
}}
animate={{
scale: 1.5,
rotate: 45,
backgroundColor: 'red',
}}
transition={{ duration: 2 }}
/>
);
}
What's happening here:
- We import
motion
from framer-motion - We create a
motion.div
instead of a regular div - The
animate
prop specifies the final state - The
transition
prop controls how the animation plays
When rendered, you'll see a blue box that animates to become a larger, rotated red box over 2 seconds.
Motion Properties
Framer Motion supports a wide range of animatable properties:
- Transform properties:
x
,y
,rotate
,scale
, etc. - CSS properties:
opacity
,backgroundColor
, etc. - SVG properties:
pathLength
,fillOpacity
, etc.
Let's see another example:
import { motion } from 'framer-motion';
function FadeInBox() {
return (
<motion.div
style={{
width: 100,
height: 100,
backgroundColor: 'purple',
}}
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1 }}
/>
);
}
This creates a box that fades in while moving down from 50 pixels above its final position.
Using Variants
For more complex animations, especially when you want to coordinate animations across multiple elements, variants are extremely useful:
import { motion } from 'framer-motion';
function AnimatedList() {
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
delayChildren: 0.3,
staggerChildren: 0.2
}
}
};
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1
}
};
return (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
style={{ listStyle: 'none' }}
>
{[1, 2, 3, 4, 5].map(i => (
<motion.li
key={i}
variants={itemVariants}
style={{
padding: '10px',
margin: '5px',
backgroundColor: '#f0f0f0',
borderRadius: '5px'
}}
>
Item {i}
</motion.li>
))}
</motion.ul>
);
}
How variants work:
- Define animation states (like "hidden" and "visible")
- Use these states in
initial
andanimate
props - Child elements inherit these states from their parent
- Use
staggerChildren
to create a sequence effect
Handling Gestures
Framer Motion makes it easy to add interactive animations with gesture support:
import { motion } from 'framer-motion';
function InteractiveButton() {
return (
<motion.button
whileHover={{
scale: 1.1,
boxShadow: "0px 5px 10px rgba(0, 0, 0, 0.2)"
}}
whileTap={{ scale: 0.95 }}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#4A90E2',
color: 'white',
fontSize: '16px',
cursor: 'pointer'
}}
>
Click me!
</motion.button>
);
}
This button scales up and adds a shadow on hover, then scales down slightly when clicked.
Working with AnimatePresence
Normally, when elements are removed from the DOM in React, they disappear instantly. AnimatePresence
allows you to animate components as they're being removed:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
function ToggleBox() {
const [isVisible, setIsVisible] = useState(true);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? "Hide" : "Show"}
</button>
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{
width: 100,
height: 100,
backgroundColor: 'green',
margin: '20px 0'
}}
/>
)}
</AnimatePresence>
</div>
);
}
This creates a toggle button that shows/hides a green box with fade animations in both directions.
Creating a Page Transition
Let's create a more practical example - a page transition effect:
import { motion } from 'framer-motion';
function PageTransition({ children }) {
const pageVariants = {
initial: {
opacity: 0,
x: "-100vw"
},
animate: {
opacity: 1,
x: 0,
transition: {
type: "spring",
stiffness: 100,
damping: 20
}
},
exit: {
opacity: 0,
x: "100vw"
}
};
return (
<motion.div
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
>
{children}
</motion.div>
);
}
To use this in a real application, you'd wrap your page components with this PageTransition
component and use it together with AnimatePresence
and your routing system.
Creating a Modal Animation
Modals are perfect for animation. Here's how to create an animated modal:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
function AnimatedModal() {
const [isOpen, setIsOpen] = useState(false);
const backdropVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
};
const modalVariants = {
hidden: {
opacity: 0,
scale: 0.8,
y: 50
},
visible: {
opacity: 1,
scale: 1,
y: 0,
transition: {
type: "spring",
stiffness: 500,
damping: 25
}
},
exit: {
opacity: 0,
scale: 0.8,
y: 50
}
};
return (
<div>
<button onClick={() => setIsOpen(true)}>
Open Modal
</button>
<AnimatePresence>
{isOpen && (
<motion.div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 100
}}
variants={backdropVariants}
initial="hidden"
animate="visible"
exit="hidden"
onClick={() => setIsOpen(false)}
>
<motion.div
style={{
width: '80%',
maxWidth: '500px',
backgroundColor: 'white',
padding: '20px',
borderRadius: '10px'
}}
variants={modalVariants}
initial="hidden"
animate="visible"
exit="exit"
onClick={e => e.stopPropagation()}
>
<h2>Modal Title</h2>
<p>This is an animated modal created with Framer Motion.</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
Working with Drag Gestures
Framer Motion provides excellent support for drag interactions:
import { motion } from 'framer-motion';
function DraggableCard() {
return (
<div style={{ height: '300px', padding: '20px', position: 'relative' }}>
<motion.div
style={{
width: '100px',
height: '100px',
backgroundColor: '#ff5252',
borderRadius: '10px',
cursor: 'grab'
}}
drag
dragConstraints={{
top: 0,
left: 0,
right: 200,
bottom: 200
}}
dragElastic={0.5}
whileDrag={{ scale: 1.1 }}
/>
</div>
);
}
This creates a draggable card that:
- Can be dragged within specific bounds
- Has elastic behavior (bounces back if dragged beyond bounds)
- Scales up while being dragged
Animation Controls
For programmatic animations, you can use the useAnimation
hook:
import { motion, useAnimation } from 'framer-motion';
import { useEffect } from 'react';
function SequenceAnimation() {
const controls = useAnimation();
async function sequence() {
await controls.start({ x: 100, transition: { duration: 1 } });
await controls.start({ y: 100, transition: { duration: 1 } });
await controls.start({ x: 0, transition: { duration: 1 } });
await controls.start({ y: 0, transition: { duration: 1 } });
}
useEffect(() => {
sequence();
}, []);
return (
<div style={{ padding: '50px', position: 'relative' }}>
<button onClick={sequence}>
Run Sequence
</button>
<motion.div
animate={controls}
style={{
width: '50px',
height: '50px',
backgroundColor: 'blue',
borderRadius: '10px',
marginTop: '20px'
}}
/>
</div>
);
}
This example creates a box that follows a square path when you click the button.
Scroll-Based Animations
One powerful feature of Framer Motion is the ability to create scroll-based animations:
import { motion, useScroll, useTransform } from 'framer-motion';
function ScrollAnimation() {
const { scrollYProgress } = useScroll();
const scale = useTransform(scrollYProgress, [0, 1], [0.2, 2]);
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0.2, 1, 0.2]);
const rotation = useTransform(scrollYProgress, [0, 1], [0, 360]);
return (
<div style={{ height: '200vh', paddingTop: '100vh' }}>
<motion.div
style={{
position: 'fixed',
top: '50%',
left: '50%',
x: '-50%',
y: '-50%',
width: '100px',
height: '100px',
backgroundColor: 'blue',
scale,
opacity,
rotate: rotation
}}
/>
<h2 style={{ position: 'fixed', bottom: '10%', width: '100%', textAlign: 'center' }}>
Scroll to animate
</h2>
</div>
);
}
This creates an element that changes scale, opacity, and rotation as the user scrolls.
Accessibility Considerations
When using animations, it's important to be mindful of accessibility:
import { motion } from 'framer-motion';
import { useState } from 'react';
function AccessibleToggle() {
const [isOn, setIsOn] = useState(false);
const toggleSwitch = () => setIsOn(!isOn);
return (
<motion.button
role="switch"
aria-checked={isOn}
style={{
width: '60px',
height: '30px',
backgroundColor: isOn ? '#4cd137' : '#ccc',
borderRadius: '15px',
display: 'flex',
padding: '2px',
cursor: 'pointer',
border: 'none'
}}
onClick={toggleSwitch}
whileTap={{ scale: 0.95 }}
transition={{ duration: 0.2 }}
>
<motion.div
style={{
width: '26px',
height: '26px',
borderRadius: '13px',
backgroundColor: 'white'
}}
animate={{
x: isOn ? 30 : 0,
transition: { type: 'spring', stiffness: 500, damping: 30 }
}}
/>
</motion.button>
);
}
This toggle switch includes appropriate ARIA attributes for screen readers.
Performance Optimization
For complex animations, performance optimization is important:
import { motion } from 'framer-motion';
function OptimizedList() {
return (
<div>
{Array.from({ length: 100 }, (_, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: 0.5,
delay: i * 0.02,
}}
style={{
height: '20px',
margin: '5px',
backgroundColor: '#e0e0e0'
}}
// Performance optimization
layoutId={`item-${i}`}
layout
whileHover={{ scale: 1.05 }}
/>
))}
</div>
);
}
Here we use:
layoutId
for stable identity when elements move- Small incremental delays to prevent too many items animating at once
Summary
Framer Motion provides a powerful yet accessible way to create animations in React applications. We've covered:
- Basic animations with the
motion
component - Using variants for more complex animations
- Handling user interactions with gesture animations
- Animating elements entering and leaving with
AnimatePresence
- Creating practical animations for common UI patterns
- Optimizing performance for smoother animations
- Making animations accessible
With these tools, you can create engaging, dynamic UIs that delight users while maintaining performance and accessibility.
Additional Resources
Exercises
- Create a loading spinner with Framer Motion that uses a repeating animation.
- Build a photo gallery that animates images as they enter and leave the viewport.
- Implement a navigation menu that slides in from the side with staggered animation for the menu items.
- Create a form with input fields that animate when they're focused and when validation errors occur.
- Build a data visualization component that animates when data values change.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)