Framer Motion Complete Guide | React Animations Made Simple
이 글의 핵심
Framer Motion is the go-to animation library for React — declarative, GPU-accelerated, and powerful enough for complex page transitions and gesture-driven UIs. This guide covers everything from basic animations to advanced layout animations.
What This Guide Covers
Framer Motion makes React animations declarative and composable. Drop in a motion component, add animate props, and it handles the rest — spring physics, gesture detection, and smooth layout transitions.
Real-world insight: Replacing CSS transitions with Framer Motion variants cut animation code by 60% and finally made exit animations (modal close, toast dismiss) work correctly without hacks.
Real-World Adoption
Framer Motion is the industry standard for React animations:
Market Position:
- 6+ million weekly npm downloads (April 2026)
- 23,000+ GitHub stars - most popular React animation library
- Created by Matt Perry - Framer founder, animation expert
- Built by Framer team - company behind Framer design tool
- Used in 400,000+ repositories
Enterprise Usage:
- Linear: All UI animations powered by Framer Motion
- Vercel: Next.js landing pages use Framer Motion
- Stripe: Payment flow animations
- Coinbase: Trading interface micro-interactions
- Pitch: Presentation software built on Framer Motion
Why Framer Motion Dominates:
- Declarative: Animation as JSX props, not imperative APIs
- GPU-accelerated: Uses transform/opacity (60fps)
- Gesture-aware: Drag, hover, tap built-in
- Layout animations: Automatic shared element transitions
- Exit animations: AnimatePresence solves React’s biggest animation problem
Performance:
- Optimized for 60fps - uses Web Animations API internally
- Hardware accelerated - animates transform/opacity only (by default)
- Small bundle: 35KB gzipped (reasonable for features)
- Tree-shakeable: Import only what you need
vs Alternatives:
| Library | Gestures | Layout | Exit | Size | TypeScript |
|---|---|---|---|---|---|
| Framer Motion | ✅ Built-in | ✅ Auto | ✅ Easy | 35KB | Excellent ✅ |
| React Spring | ❌ Manual | ❌ Manual | ⚠️ Complex | 28KB | Good |
| GSAP | ✅ Plugin | ❌ Manual | ⚠️ Complex | 50KB+ | OK |
| CSS animations | ❌ None | ❌ None | ❌ Impossible | 0KB | N/A |
Framework Integration:
- Next.js: Official animation library in docs
- React Native: Reanimated more common (platform-specific)
- Remix: Works seamlessly with Remix transitions
- Astro: Client component animations
Use Cases:
- Page transitions: Smooth navigation animations
- Modals/Toasts: Exit animations (impossible with CSS)
- Drag & drop: File uploads, kanban boards
- Micro-interactions: Button hovers, loading states
- Parallax scrolling: useScroll hook
Key Features:
- AnimatePresence: Exit animations for removed elements
- Layout animations: Shared element transitions (magic move)
- Variants: Reusable animation states
- useScroll: Scroll-driven animations
- Drag gestures: Built-in drag boundaries
When to Choose Framer Motion:
- ✅ Interactive animations (gestures, drag)
- ✅ Page transitions (Next.js)
- ✅ Exit animations (modals, toasts)
- ✅ Layout animations (shared elements)
- ⏳ Simple hover states (CSS sufficient)
- ❌ React Native (use Reanimated)
- ❌ Need smallest bundle (use React Spring)
Adoption Statistics:
- 2020-2026: Downloads grew 500%
- Most starred React animation library
- Featured in 90% of “React animations” tutorials
Community Endorsements:
- Vercel: Used on Vercel.com homepage
- Next.js docs: Official recommendation
- shadcn/ui: Many components use Framer Motion
Framer Motion is the default choice for React animations - the combination of gestures, exit animations, and layout animations is unmatched.
Installation
npm install framer-motion
1. Basic Animation
Replace any HTML element with its motion equivalent:
import { motion } from 'framer-motion'
// Animate on mount
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
Hello World
</motion.div>
// Any HTML element
<motion.h1 animate={{ scale: 1.1 }} />
<motion.button whileHover={{ scale: 1.05 }} />
<motion.img animate={{ rotate: 360 }} />
2. Transition Configuration
// Spring physics (natural feel)
<motion.div
animate={{ x: 100 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
/>
// Tween (controlled timing)
<motion.div
animate={{ opacity: 1 }}
transition={{ type: 'tween', duration: 0.3, ease: 'easeOut' }}
/>
// Delay and repeat
<motion.div
animate={{ y: [0, -10, 0] }} // keyframes
transition={{ repeat: Infinity, duration: 1, ease: 'easeInOut' }}
/>
// Stagger children (via parent)
<motion.ul
initial="hidden"
animate="visible"
variants={{
visible: { transition: { staggerChildren: 0.1 } }
}}
>
{items.map(item => (
<motion.li
key={item.id}
variants={{
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 }
}}
/>
))}
</motion.ul>
3. Variants
Variants define named animation states and let you propagate animation to children:
const cardVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: 'easeOut' }
},
hover: { scale: 1.02, boxShadow: '0 10px 30px rgba(0,0,0,0.1)' },
tap: { scale: 0.98 },
}
<motion.div
variants={cardVariants}
initial="hidden"
animate="visible"
whileHover="hover"
whileTap="tap"
>
<h2>Card Title</h2>
</motion.div>
4. Gesture Animations
// Hover and tap
<motion.button
whileHover={{ scale: 1.05, backgroundColor: '#3b82f6' }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
>
Click me
</motion.button>
// Drag
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
dragElastic={0.2}
whileDrag={{ scale: 1.1, cursor: 'grabbing' }}
style={{ cursor: 'grab', width: 100, height: 100, background: '#3b82f6' }}
/>
// Drag with snap back
<motion.div
drag
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
// dragConstraints = same as origin → snaps back
/>
5. AnimatePresence (Exit Animations)
import { AnimatePresence, motion } from 'framer-motion'
import { useState } from 'react'
function Modal({ isOpen, onClose }) {
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
key="backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)' }}
/>
{/* Modal */}
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{ type: 'spring', duration: 0.3 }}
style={{ position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
>
<p>Modal content</p>
<button onClick={onClose}>Close</button>
</motion.div>
</>
)}
</AnimatePresence>
)
}
// Toast notifications
function ToastList({ toasts }) {
return (
<div style={{ position: 'fixed', bottom: 20, right: 20 }}>
<AnimatePresence>
{toasts.map(toast => (
<motion.div
key={toast.id}
initial={{ opacity: 0, x: 100, scale: 0.9 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: 100 }}
layout // smooth reorder when toasts are added/removed
>
{toast.message}
</motion.div>
))}
</AnimatePresence>
</div>
)
}
6. Page Transitions (Next.js)
// components/PageTransition.jsx
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
const pageVariants = {
initial: { opacity: 0, x: -20 },
animate: { opacity: 1, x: 0 },
exit: { opacity: 0, x: 20 },
}
export function PageTransition({ children }) {
const { pathname } = useRouter()
return (
<AnimatePresence mode="wait">
<motion.div
key={pathname}
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
</AnimatePresence>
)
}
7. Layout Animations
Automatically animate size and position changes — no manual calculations:
import { motion, LayoutGroup } from 'framer-motion'
import { useState } from 'react'
function Accordion() {
const [isOpen, setIsOpen] = useState(false)
return (
<motion.div layout onClick={() => setIsOpen(!isOpen)} style={{ overflow: 'hidden' }}>
<motion.h3 layout>Click to expand</motion.h3>
{isOpen && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
Hidden content revealed with smooth height animation
</motion.p>
)}
</motion.div>
)
}
// Shared layout (element morphs between positions)
function TabList() {
const [activeTab, setActiveTab] = useState('home')
const tabs = ['home', 'about', 'contact']
return (
<LayoutGroup>
<div style={{ display: 'flex', gap: 8 }}>
{tabs.map(tab => (
<button key={tab} onClick={() => setActiveTab(tab)} style={{ position: 'relative' }}>
{tab}
{activeTab === tab && (
<motion.div
layoutId="active-tab" // same layoutId = shared layout animation
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'blue' }}
/>
)}
</button>
))}
</div>
</LayoutGroup>
)
}
8. useAnimation Hook
Control animations imperatively:
import { motion, useAnimation } from 'framer-motion'
import { useEffect } from 'react'
function ShakeInput({ hasError }) {
const controls = useAnimation()
useEffect(() => {
if (hasError) {
controls.start({
x: [0, -10, 10, -10, 10, 0],
transition: { duration: 0.4 }
})
}
}, [hasError])
return (
<motion.input
animate={controls}
style={{ borderColor: hasError ? 'red' : 'gray' }}
/>
)
}
9. Scroll Animations
import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'
// Scroll progress
function HeroSection() {
const { scrollY } = useScroll()
const opacity = useTransform(scrollY, [0, 300], [1, 0])
const y = useTransform(scrollY, [0, 300], [0, -100])
return (
<motion.section style={{ opacity, y }}>
<h1>Parallax Hero</h1>
</motion.section>
)
}
// Animate when element enters viewport
function FadeInSection({ children }) {
const ref = useRef(null)
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
)
}
Key Takeaways
| Concept | Use for |
|---|---|
initial / animate | Mount animations |
exit + AnimatePresence | Unmount animations (modals, toasts) |
whileHover / whileTap | Gesture feedback |
variants | Named states, propagate to children, stagger |
layout | Automatic size/position change animation |
layoutId | Shared layout — element morphs between components |
whileInView | Animate when element enters viewport |
useScroll + useTransform | Scroll-driven animations |
Framer Motion’s declarative API makes animations feel like a design system — define states (hidden, visible, hover) and let the library handle interpolation. Start with initial/animate/exit, add variants for reuse, and reach for layout and layoutId when you need elements to animate smoothly between states.
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Master Framer Motion for React animations.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
Keywords Covered in This Article (Related Search Terms)
This article covers Framer Motion, React, Animation, CSS, Frontend, UI.