Framer Motion은 React에서 선언적이고 직관적인 애니메이션을 제공하는 라이브러리입니다. 컴포넌트 상태와 상호작용을 자연스럽게 연결하고, 레이아웃 변경·마운트/언마운트 전환까지 손쉽게 다룰 수 있습니다. 아래 예제와 패턴을 기준으로 실무에 바로 적용해보세요.
1. 설치 및 기본 세팅
프로젝트에 Framer Motion을 설치합니다.
// npm
a npm i framer-motion
// yarn
yarn add framer-motion일반적인 React 환경에서 추가 설정 없이 바로 사용할 수 있습니다. Next.js 등 SSR 환경에서도 기본적으로 호환되며, 무거운 애니메이션 컴포넌트는 동적 로딩을 고려합니다.
2. 가장 기본: motion 컴포넌트
motion.* 요소에 initial, animate, transition을 선언해 자연스러운 입장 애니메이션을 구현합니다.
import { motion } from "framer-motion";
export function IntroBox() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
style={{ background: "#f5f5f5", padding: 16, borderRadius: 8 }}
>
안녕하세요! Framer Motion입니다.
</motion.div>
);
}3. Variants로 상태 기반 모션 설계
Variants는 여러 상태를 선언적으로 관리해 컴포넌트와 자식들의 애니메이션을 일관성 있게 제어합니다.
import { motion } from "framer-motion";
const listVariants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.1, delayChildren: 0.1 }
}
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 }
};
export function StaggerList({ items }) {
return (
<motion.ul variants={listVariants} initial="hidden" animate="show">
{items.map((text) => (
<motion.li key={text} variants={itemVariants}>{text}</motion.li>
))}
</motion.ul>
);
}4. 마운트/언마운트 전환: AnimatePresence
조건부 렌더링 시 컴포넌트가 사라질 때도 exit 애니메이션을 적용합니다. 모달, 토스트 등에 유용합니다.
import { motion, AnimatePresence } from "framer-motion";
export function Modal({ open, onClose }) {
return (
<AnimatePresence>
{open && (
<motion.div
className="backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)" }}
onClick={onClose}
>
<motion.div
role="dialog"
aria-modal="true"
initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
style={{ width: 420, margin: "10% auto", background: "#fff", padding: 24, borderRadius: 12 }}
onClick={(e) => e.stopPropagation()}
>
<h2>설정</h2>
<button onClick={onClose}>닫기</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}5. 제스처 및 드래그 인터랙션
호버/탭, 드래그 같은 사용자의 입력에 실시간 반응하는 모션을 손쉽게 구현합니다.
import { motion } from "framer-motion";
export function ActionButton({ children }) {
return (
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
style={{ padding: "10px 16px", borderRadius: 8 }}
>
{children}
</motion.button>
);
}
export function DraggableCard() {
return (
<motion.div
drag
dragConstraints={{ left: -60, right: 60, top: -40, bottom: 40 }}
style={{ width: 160, height: 100, background: "#e0f7fa", borderRadius: 12 }}
/>
);
}6. 레이아웃 애니메이션과 공유 레이아웃
DOM 레이아웃 변경에도 부드럽게 전환되며, 두 화면 간 동일한 요소를 연결해 자연스러운 전환을 제공합니다.
import { motion, AnimatePresence } from "framer-motion";
export function ExpandingPanel({ open }) {
return (
<motion.div layout style={{ background: "#fafafa", borderRadius: 12, padding: 12 }}>
<h4>레이아웃 전환</h4>
<AnimatePresence>
{open && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
상세 내용이 레이아웃 변화에 맞춰 자연스럽게 표시됩니다.
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
}
export function SharedAvatar({ showDetail, onBack }) {
return (
<AnimatePresence mode="wait">
{showDetail ? (
<motion.div key="detail" style={{ padding: 24 }}>
<motion.img layoutId="avatar" src="/avatar.png" alt="사용자" style={{ width: 160, borderRadius: "50%" }} />
<button onClick={onBack}>뒤로</button>
</motion.div>
) : (
<motion.div key="list" style={{ display: "flex", gap: 12, alignItems: "center" }}>
<motion.img layoutId="avatar" src="/avatar.png" alt="사용자" style={{ width: 48, borderRadius: "50%" }} />
<span>홍길동</span>
</motion.div>
)}
</AnimatePresence>
);
}7. 스크롤 기반 애니메이션
스크롤 진행도에 따라 스타일을 매핑해 진행 바, 섹션 페이드인 등 구현이 가능합니다.
import { motion, useScroll, useTransform } from "framer-motion";
export function ScrollProgressBar() {
const { scrollYProgress } = useScroll();
const scaleX = useTransform(scrollYProgress, [0, 1], [0, 1]);
return (
<motion.div style={{ position: "fixed", top: 0, left: 0, right: 0, height: 4, background: "#eee" }}>
<motion.div style={{ height: "100%", background: "#0070f3", transformOrigin: "0% 50%", scaleX }} />
</motion.div>
);
}8. 접근성: 사용자 설정 존중
사용자의 모션 감소 설정을 감지해 대체 애니메이션을 제공하면 접근성이 향상됩니다.
import { motion, useReducedMotion } from "framer-motion";
export function AccessibleFadeIn({ children }) {
const shouldReduce = useReducedMotion();
const initial = shouldReduce ? { opacity: 0 } : { opacity: 0, y: 12 };
const animate = shouldReduce ? { opacity: 1 } : { opacity: 1, y: 0 };
return (
<motion.div initial={initial} animate={animate} transition={{ duration: 0.3 }}>
{children}
</motion.div>
);
}9. 실무 성능 최적화 체크리스트
애니메이션은 UI 반응성을 저하시킬 수 있으므로 다음을 확인합니다.
1) transform/opacity 위주로 애니메이션합니다. width/height/top/left 변경은 레이아웃 계산 비용이 큽니다.
2) Variants와 부모-자식 제어로 렌더 트리를 단순화합니다.
3) 대량 항목은 지연(stagger), 가상화, 조건부 애니메이션으로 초기 비용을 줄입니다.
4) 애니메이션 중 복잡한 상태 업데이트를 피하고, memo로 불필요한 재렌더를 줄입니다.
5) SSR/Next.js에서 무거운 섹션은 동적 import로 초기 페인트를 가볍게 합니다.
10. 자주 쓰는 패턴 요약
• 페이지 진입 페이드+슬라이드: initial/animate 조합으로 간단히 구현합니다.
• 리스트 항목 순차 등장: 부모/자식 variants와 staggerChildren 사용.
• 모달/토스트: AnimatePresence로 exit 처리.
• 공유 요소 전환: layoutId로 두 화면 사이의 동일 객체 연결.
• 스크롤 트리거: useScroll+useTransform으로 진행도 기반 애니메이션.
11. 도입 전략
컴포넌트 설계 단계에서 인터랙션 목적을 먼저 정의하고, 성능·접근성 기준을 체크한 뒤 프레임 단위로 모션을 설계합니다. 작은 단위에서 시작해 공통 variants와 전환 규칙을 라이브러리화하면 유지보수가 쉬워집니다.
Framer Motion은 선언적 설계, 강력한 레이아웃 전환, 제스처 반응성까지 모두 갖춘 실무 친화적인 도구입니다. 위 패턴을 기준으로 프로젝트에 점진적으로 도입해보세요.
'React' 카테고리의 다른 글
| React 앱에서 사용자 권한(Role-Based Access Control) 처리하기 (0) | 2026.05.12 |
|---|---|
| React에서 Progressive Image Loading 구현하기 (0) | 2026.05.11 |
| React에서 Highcharts로 데이터 시각화하기 (1) | 2026.05.08 |
| React 컴포넌트를 Storybook에서 문서화하고 테스트하기 (1) | 2026.05.08 |
| React 앱에서 데이터 페칭 시 Skeleton UI 구현하기 (0) | 2026.05.07 |