Skip to main content

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:

bash
npm install framer-motion
# or
yarn add framer-motion

Basic Concepts

Framer Motion is built around a few core components and concepts:

  1. motion components: These are enhanced HTML/SVG elements with animation capabilities
  2. Variants: Predefined animation states that can be reused
  3. Gestures: Built-in support for hover, tap, drag, and other interactions
  4. 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:

jsx
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:

  1. We import motion from framer-motion
  2. We create a motion.div instead of a regular div
  3. The animate prop specifies the final state
  4. 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:

jsx
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:

jsx
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:

  1. Define animation states (like "hidden" and "visible")
  2. Use these states in initial and animate props
  3. Child elements inherit these states from their parent
  4. Use staggerChildren to create a sequence effect

Handling Gestures

Framer Motion makes it easy to add interactive animations with gesture support:

jsx
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:

jsx
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:

jsx
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:

jsx
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:

jsx
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:

  1. Can be dragged within specific bounds
  2. Has elastic behavior (bounces back if dragged beyond bounds)
  3. Scales up while being dragged

Animation Controls

For programmatic animations, you can use the useAnimation hook:

jsx
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:

jsx
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:

jsx
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:

jsx
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

  1. Create a loading spinner with Framer Motion that uses a repeating animation.
  2. Build a photo gallery that animates images as they enter and leave the viewport.
  3. Implement a navigation menu that slides in from the side with staggered animation for the menu items.
  4. Create a form with input fields that animate when they're focused and when validation errors occur.
  5. 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! :)