Table of Contents
- Getting Started: Installation & Setup
- Core Concepts: Motion Components & Basic Animation
- Variants: Reusable Animation Definitions
- Transitions: Controlling Animation Timing & Easing
- Interactive Gestures: Hover, Tap, & Drag
- Layout Animations: Automating Size & Position Changes
- Animation Orchestration: Stagger, Sequence, & Parallel
- Performance Best Practices
- Real-World Example: Animated Modal
- Conclusion
- References
1. Getting Started: Installation & Setup
Before we dive into animations, let’s set up Framer Motion in a React project.
Step 1: Install Framer Motion
First, install the library via npm or yarn:
# Using npm
npm install framer-motion
# Using yarn
yarn add framer-motion
Step 2: Basic Setup
Framer Motion works by wrapping React elements with its motion component. Import motion from framer-motion and use it to animate any HTML or SVG element.
Let’s start with a simple “Hello World” animation: a div that fades in and slides up when the component mounts.
import { motion } from "framer-motion";
function App() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }} // Start state: invisible, 20px below
animate={{ opacity: 1, y: 0 }} // End state: visible, in place
transition={{ duration: 0.5 }} // Animation timing: 500ms
>
Hello, Framer Motion!
</motion.div>
);
}
export default App;
In this example:
initial: Defines the starting state of the animation (before it runs).animate: Defines the target state (what the element animates to).transition: Controls timing, easing, and other animation behavior.
2. Core Concepts: Motion Components & Basic Animation
At the heart of Framer Motion is the motion component. It wraps standard HTML/SVG elements (like div, span, svg, etc.) and adds animation-specific props.
Key Props for Basic Animation
| Prop | Purpose |
|---|---|
initial | Starting state of the animation (e.g., { opacity: 0, scale: 0.8 }). |
animate | Target state to animate to (e.g., { opacity: 1, scale: 1 }). |
exit | State for when the component unmounts (requires AnimatePresence). |
transition | Configures duration, easing, delay, and physics (e.g., { duration: 0.3, ease: "easeOut" }). |
Example: Fade & Scale Animation
Let’s animate a card that fades in and scales up on mount:
import { motion } from "framer-motion";
const Card = () => (
<motion.div
className="card"
style={{ width: 200, height: 150, padding: 20, background: "#fff", borderRadius: 8, boxShadow: "0 4px 6px rgba(0,0,0,0.1)" }}
initial={{ opacity: 0, scale: 0.9 }} // Start: transparent, slightly scaled down
animate={{ opacity: 1, scale: 1 }} // End: opaque, full size
transition={{ duration: 0.4, ease: [0.25, 0.1, 0.25, 1] }} // Smooth "easeOut" curve
>
Animated Card
</motion.div>
);
export default Card;
Here, ease: [0.25, 0.1, 0.25, 1] uses a cubic-bezier curve for a natural,缓急有致 (gradual acceleration/deceleration) feel.
3. Variants: Reusable Animation Definitions
As animations grow more complex, repeating initial/animate props across components becomes tedious. Variants solve this by letting you define reusable animation states.
Defining Variants
A variant is an object where keys represent animation states (e.g., hidden, visible), and values are the properties to animate:
const fadeInVariant = {
hidden: { opacity: 0, y: 20 }, // Start state
visible: { opacity: 1, y: 0 } // End state
};
Using Variants
Pass the variant to the motion component and reference the states via initial, animate, or exit:
import { motion } from "framer-motion";
const fadeInVariant = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const FadeInElement = () => (
<motion.div
variants={fadeInVariant}
initial="hidden" // Start in "hidden" state
animate="visible" // Animate to "visible" state
transition={{ duration: 0.5 }}
>
I fade in!
</motion.div>
);
Variants promote reusability—you can share them across components or even import them from a separate file (e.g., animations/variants.js).
4. Transitions: Controlling Animation Timing & Easing
The transition prop lets you fine-tune how animations behave. Beyond duration and ease, you can control delays, physics-based motion, and more.
Common Transition Options
| Option | Description |
|---|---|
duration | Length of the animation in seconds (e.g., 0.5 for 500ms). |
delay | Delay before the animation starts (e.g., 0.2 for 200ms delay). |
ease | Easing function (e.g., "linear", "easeInOut", or a cubic-bezier array). |
type | Animation type: "tween" (default, linear interpolation) or "spring" (physics-based). |
stiffness | For spring animations: Higher values = stiffer (less bouncy) motion. |
damping | For spring animations: Higher values = less oscillation. |
Example: Spring Animation
For natural, physics-driven motion (e.g., a button that “springs” when clicked), use type: "spring":
<motion.button
whileTap={{ scale: 0.95 }} // Animate on tap
transition={{ type: "spring", stiffness: 300, damping: 20 }}
>
Click Me
</motion.button>
Here, stiffness: 300 makes the spring tight, and damping: 20 reduces bounce, creating a snappy, responsive feel.
5. Interactive Gestures: Hover, Tap, & Drag
Framer Motion simplifies adding interactive animations with built-in gesture props. These props trigger animations in response to user input.
Key Gesture Props
| Prop | Trigger |
|---|---|
whileHover | Animate when the user hovers over the element. |
whileTap | Animate when the user clicks/taps the element. |
whileDrag | Animate while the user drags the element (requires drag prop). |
Example: Interactive Button
import { motion } from "framer-motion";
const InteractiveButton = () => (
<motion.button
className="btn"
whileHover={{ scale: 1.05, boxShadow: "0 8px 16px rgba(0,0,0,0.2)" }} // Hover: scale up, shadow
whileTap={{ scale: 0.98 }} // Tap: scale down slightly
transition={{ type: "spring", stiffness: 400, damping: 10 }}
style={{ padding: "12px 24px", border: "none", borderRadius: 4, background: "#007bff", color: "white", cursor: "pointer" }}
>
Hover or Tap Me
</motion.button>
);
This button feels alive: it grows subtly on hover and “presses” down on tap, mimicking physical buttons.
6. Layout Animations: Automating Size & Position Changes
One of Framer Motion’s most powerful features is layout animations—automatically animating elements when their size or position changes (e.g., adding items to a list, resizing a panel).
The layout Prop
Add layout (or layoutId for cross-component transitions) to a motion element, and Framer Motion will animate its position/size when it updates:
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
const LayoutAnimationExample = () => {
const [showDetails, setShowDetails] = useState(false);
return (
<div>
<button onClick={() => setShowDetails(!showDetails)}>
Toggle Details
</button>
<motion.div
layout // Animate size/position changes
style={{
background: "#f0f0f0",
padding: showDetails ? "20px" : "10px",
borderRadius: 8
}}
>
Base Content
{showDetails && <p>Extra details here!</p>}
</motion.div>
</div>
);
};
When showDetails toggles, the div smoothly animates its height to fit the new content—no manual calculations required!
Cross-Component Layout Transitions with layoutId
Use layoutId to animate elements between different components (e.g., a card expanding into a full-page view). Both elements must share the same layoutId:
// ListItem.jsx
const ListItem = ({ item }) => (
<motion.div layoutId={item.id} className="list-item">
{item.title}
</motion.div>
);
// DetailView.jsx
const DetailView = ({ item }) => (
<motion.div layoutId={item.id} className="detail-view">
<h1>{item.title}</h1>
<p>{item.description}</p>
</motion.div>
);
When navigating from ListItem to DetailView, Framer Motion animates the element’s size, position, and even styling (e.g., borderRadius) between the two components.
7. Animation Orchestration: Stagger, Sequence, & Parallel
For complex UIs (e.g., grids, lists), you’ll often want to orchestrate animations—staggering delays, chaining sequences, or running animations in parallel.
Staggering Animations
Use staggerChildren in a parent variant to stagger animations for child elements. Pair it with delayChildren to control the initial delay:
import { motion } from "framer-motion";
// Parent variant with stagger configuration
const parentVariant = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1, // Stagger child animations by 100ms
delayChildren: 0.2 // Start staggering after 200ms
}
}
};
// Child variant (reused for each item)
const childVariant = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
};
const StaggeredList = () => (
<motion.ul variants={parentVariant} initial="hidden" animate="visible">
{["Item 1", "Item 2", "Item 3", "Item 4"].map((item, index) => (
<motion.li
key={index}
variants={childVariant}
transition={{ duration: 0.3 }}
style={{ listStyle: "none", padding: "8px", margin: "4px", background: "#f0f0f0" }}
>
{item}
</motion.li>
))}
</motion.ul>
);
Here, the list items fade in one after another with a 100ms delay between them.
Sequences & Parallel Animations
Use the when transition option to chain animations (run one after another) or run them in parallel:
const sequenceVariant = {
first: { opacity: 0 },
second: { opacity: 1, transition: { when: "beforeChildren" } }, // Run before children
third: { opacity: 1, transition: { when: "afterChildren" } } // Run after children
};
For more control, use withSequence or withParallel from framer-motion:
import { motion, withSequence, withParallel } from "framer-motion";
const complexTransition = {
x: withSequence(0, 100, 50), // Animate x: 0 → 100 → 50 (sequence)
opacity: withParallel(0, 1) // Animate opacity: 0 → 1 (parallel with x)
};
8. Performance Best Practices
Framer Motion is optimized for performance, but poor implementation can still cause jank. Follow these tips:
1. Animate transform and opacity Only
Browsers optimize transform (e.g., translate, scale) and opacity animations via hardware acceleration. Avoid animating properties like width, height, or margin—these trigger expensive layout recalculations.
2. Use willChange Sparingly
While willChange: "transform" hints to the browser to prepare for animation, overusing it wastes resources. Let Framer Motion handle this automatically where possible.
3. Avoid Layout Thrashing
If reading layout properties (e.g., offsetHeight) during animations, batch reads/writes to prevent forced synchronous layouts.
4. Leverage AnimatePresence for Exit Animations
For components that unmount (e.g., modals, tabs), wrap them in AnimatePresence to ensure exit animations complete before removal:
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
const Modal = ({ isOpen, onClose }) => (
<AnimatePresence>
{isOpen && (
<motion.div
key="modal" // Required for AnimatePresence to track the component
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)" }}
>
<motion.div
initial={{ scale: 0.9 }}
animate={{ scale: 1 }}
exit={{ scale: 0.9 }}
onClick={(e) => e.stopPropagation()}
style={{ background: "white", padding: 20, margin: "100px auto", maxWidth: 500 }}
>
Modal Content
<button onClick={onClose}>Close</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
9. Real-World Example: Animated Modal
Let’s combine everything we’ve learned to build a production-ready animated modal. This modal will:
- Fade in a semi-transparent backdrop.
- Scale up the modal content from 90% to 100% size.
- Animate out smoothly when closed.
Step 1: Define Variants
// animations/modalVariants.js
export const backdropVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
exit: { opacity: 0 }
};
export const modalContentVariants = {
hidden: { scale: 0.9, opacity: 0 },
visible: { scale: 1, opacity: 1 },
exit: { scale: 0.9, opacity: 0 }
};
Step 2: Build the Modal Component
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import { backdropVariants, modalContentVariants } from "./animations/modalVariants";
const AnimatedModal = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
variants={backdropVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={{ duration: 0.3 }}
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: "rgba(0, 0, 0, 0.5)",
zIndex: 100
}}
onClick={() => setIsOpen(false)} // Close on backdrop click
/>
{/* Modal Content */}
<motion.div
variants={modalContentVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={{ type: "spring", stiffness: 300, damping: 20 }}
style={{
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
background: "white",
padding: "2rem",
borderRadius: "8px",
zIndex: 101,
width: "90%",
maxWidth: "500px"
}}
onClick={(e) => e.stopPropagation()} // Prevent closing when clicking inside modal
>
<h2>Animated Modal</h2>
<p>This modal fades in and scales up smoothly!</p>
<button onClick={() => setIsOpen(false)} style={{ marginTop: "1rem" }}>
Close
</button>
</motion.div>
</>
)}
</AnimatePresence>
</div>
);
};
export default AnimatedModal;
10. Conclusion
Framer Motion empowers React developers to create stunning, performant animations with minimal effort. By leveraging its declarative API, reusable variants, and powerful features like layout animations and gesture support, you can elevate your UI from static to dynamic.
Start small: experiment with basic motion components and variants, then gradually explore advanced features like orchestration and cross-component transitions. With Framer Motion, the only limit is your creativity!