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.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Master Framer Motion for React animations. Covers motion components, variants, gestures, page transitions, layout animat… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [React 18 Deep Dive | Concurrent Features· Suspense](/en/blog/react-18-deep-dive/
- [Tailwind CSS Complete Guide](/en/blog/tailwind-css-complete-guide-en/
이 글에서 다루는 키워드 (관련 검색어)
Framer Motion, React, Animation, CSS, Frontend, UI 등으로 검색하시면 이 글이 도움이 됩니다.