Framer Motion
프레이머 모션
Framer Motion은 React용 애니메이션 라이브러리로, 선언적 문법과 강력한 제스처 지원으로 복잡한 UI 애니메이션을 간단하게 구현합니다. 물리 기반 스프링 애니메이션과 레이아웃 전환이 특징입니다.
프레이머 모션
Framer Motion은 React용 애니메이션 라이브러리로, 선언적 문법과 강력한 제스처 지원으로 복잡한 UI 애니메이션을 간단하게 구현합니다. 물리 기반 스프링 애니메이션과 레이아웃 전환이 특징입니다.
Framer Motion은 Framer 디자인 툴에서 파생된 React 애니메이션 라이브러리로, 2019년 첫 출시 이후 React 생태계에서 가장 인기 있는 모션 라이브러리가 되었습니다. motion 컴포넌트를 통해 HTML/SVG 요소에 애니메이션을 선언적으로 추가할 수 있으며, CSS transition이나 keyframes보다 훨씬 직관적인 API를 제공합니다.
핵심 특징은 물리 기반 스프링 애니메이션입니다. duration 대신 stiffness(강성), damping(감쇠), mass(질량) 파라미터로 자연스럽고 탄성 있는 움직임을 구현합니다. 이는 실제 물리 시뮬레이션을 기반으로 하여, duration 기반 이징보다 더 자연스럽고 반응적인 느낌을 줍니다.
variants 시스템은 복잡한 컴포넌트 애니메이션을 관리하는 강력한 패턴입니다. 부모 요소의 상태 변화가 자식 요소로 자동 전파되어, 계층적인 애니메이션을 쉽게 오케스트레이션할 수 있습니다. staggerChildren, delayChildren 옵션으로 순차 애니메이션도 간단히 구현됩니다.
AnimatePresence 컴포넌트는 React에서 가장 어려운 문제인 exit 애니메이션을 해결합니다. 조건부 렌더링으로 DOM에서 제거되는 컴포넌트에도 fade-out, slide-out 등의 퇴장 애니메이션을 적용할 수 있습니다. layout prop을 사용하면 요소의 위치/크기 변화도 자동으로 부드럽게 전환됩니다.
import { motion, AnimatePresence } from 'framer-motion';
// 기본 애니메이션
function FadeInCard() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="card"
>
카드 내용
</motion.div>
);
}
// 스프링 애니메이션 with hover/tap
function SpringButton() {
return (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
>
클릭하세요
</motion.button>
);
}
// Variants를 사용한 리스트 애니메이션
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
};
const itemVariants = {
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 },
};
function AnimatedList({ items }) {
return (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
>
{items.map((item) => (
<motion.li key={item.id} variants={itemVariants}>
{item.name}
</motion.li>
))}
</motion.ul>
);
}
// AnimatePresence로 exit 애니메이션
function Modal({ isOpen, onClose }) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
className="modal-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
<motion.div
className="modal-content"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
>
모달 내용
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
// Layout 애니메이션
function ExpandableCard({ isExpanded }) {
return (
<motion.div
layout
className={isExpanded ? 'card-expanded' : 'card-collapsed'}
transition={{ layout: { duration: 0.3 } }}
>
<motion.h2 layout="position">제목</motion.h2>
{isExpanded && <p>확장된 내용...</p>}
</motion.div>
);
}