framer-motion은 React 기반 UI에 자연스럽고 강력한 애니메이션을 손쉽게 추가할 수 있게 해주는 모션 라이브러리입니다. 특히 상태 기반 인터랙션과 레이아웃 전환에 최적화되어 있어, 실제 서비스에 적합한 고퀄리티 애니메이션을 빠르게 구현할 수 있습니다.
Motion - Web animations for JavaScript, React and Vue
Animations made simple. The fast and light animation library for JS, React and Vue. Motion uses browser APIs like WAAPI and ScrollTimeline for a tiny filesize and superfast performance. Previously Framer Motion.
motion.dev
어디에 잘 쓰일까?
- 버튼 클릭 → 메뉴 슬라이드 열기
- 마운트/언마운트 시 fade in/out
- 드래그 & 드롭 인터페이스
- 페이지 전환 시 부드러운 트랜지션
- 반응형 UI에서 레이아웃 전환 효과
Framer Motion의 주요 기능
1. 선언형 애니메이션
<motion.div animate={{ x: 100 }} />
- React의 상태(state)에 따라 자동으로 애니메이션 적용
2. 모션 컴포넌트 (motion.div 등)
- HTML 요소나 SVG 요소를 motion.*으로 감싸서 애니메이션 가능
3. 상태 기반 애니메이션 (animate, initial, exit)
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
- 상태 전환에 따라 자연스럽게 진입/퇴장 애니메이션 적용
4. 드래그/제스처 지원
<motion.div drag />
- 마우스나 터치 기반 드래그 기능 제공
5. Layout Animation (자동 레이아웃 애니메이션)
<motion.div layout />
- 요소 위치 변경 시 애니메이션으로 자연스럽게 전환
6. AnimatePresence
- 조건부 렌더링 시 요소의 입장/퇴장을 부드럽게 처리
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
7. Variants (애니메이션 상태 사전 정의)
const variants = {
open: { x: 0 },
closed: { x: "-100%" },
};
<motion.div variants={variants} animate={isOpen ? "open" : "closed"} />
| 직관적인 문법 | CSS보다 간단하게 동작 정의 가능 |
| React 친화적 | 상태 기반 렌더링과 자연스럽게 통합 |
| 고성능 | requestAnimationFrame 기반으로 부드러운 렌더링 |
| 커뮤니티와 문서 | 공식 문서, 튜토리얼, 사례가 풍부함 |
1단계: 기본 개념과 동작 이해 (초급)
1. Framer Motion이란?
개념
- Framer Motion은 React를 위한 선언형 애니메이션 라이브러리입니다.
- CSS 애니메이션이나 GSAP 등과 달리, 컴포넌트 레벨에서 상태 기반으로 제어할 수 있는 것이 강점입니다.
- 기본적으로 spring physics를 활용해 부드러운 모션을 제공합니다.
특징 요약
- React 친화적인 문법 (motion.div, initial, animate, exit)
- 상태에 따라 선언적으로 애니메이션 실행
- 드래그, 제스처 등 상호작용 지원
- Variants, layout, AnimatePresence 등 풍부한 기능
2. React + CSS 애니메이션 vs Framer Motion 차이
비교 항목 CSS 애니메이션 Framer Motion
| 선언 방식 | 클래스 기반 (.fade-in { animation...}) | 컴포넌트 props 기반 (<motion.div animate={...} />) |
| 조건부 렌더링 애니메이션 | 복잡함 (setTimeout, class 토글 등) | AnimatePresence로 자연스럽게 처리 |
| 상태 기반 애니메이션 | JS와 분리되어 복잡 | 상태와 애니메이션이 통합됨 |
| 고급 제스처 지원 | 별도 라이브러리 필요 | 기본 지원 (drag, hover, tap) |
| 유지 보수 | 애니메이션 로직이 흩어짐 | 컴포넌트 내부에서 일관되게 관리됨 |
3. 설치 및 기본 사용법
설치
npm install framer-motion
또는 bun:
bun add framer-motion
기본 구조
import { motion } from "framer-motion";
export default function Box() {
return (
<motion.div
initial={{ opacity: 0 }} // 처음 상태
animate={{ opacity: 1 }} // 애니메이션할 상태
transition={{ duration: 1 }} // 전환 효과
>
Hello, Framer Motion!
</motion.div>
);
}
4. 핵심 Props 정리
1) motion 컴포넌트
- Framer Motion이 제공하는 wrapper
- 기존 DOM 요소에 애니메이션 기능 부여
<motion.div />
<motion.button />
<motion.img />
2) initial
- 컴포넌트가 처음 렌더링될 때 상태
initial={{ scale: 0.5, opacity: 0 }}
3) animate
- 애니메이션이 도달할 상태
- React state에 따라 동적으로 변할 수 있음
animate={{ scale: 1, opacity: 1 }}
4) exit
- 컴포넌트가 DOM에서 제거될 때 상태
- AnimatePresence와 함께 사용
exit={{ opacity: 0 }}
5) transition
- 애니메이션의 속도, 곡선 등을 설정
transition={{ duration: 0.5, ease: "easeOut" }}
실습 예제 1: 페이드 인 카드
import { motion } from 'framer-motion';
export default function FadeInCard() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1.2 }}
style={{
width: 200,
height: 120,
backgroundColor: '#007bff',
borderRadius: 10,
color: 'white',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
Welcome!
</motion.div>
);
}
실습 예제 2: 마운트/언마운트 애니메이션 (with AnimatePresence)
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
export default function ToggleBox() {
const [visible, setVisible] = useState(true);
return (
<>
<button onClick={() => setVisible(!visible)}>Toggle</button>
<AnimatePresence>
{visible && (
<motion.div
key="box"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.3 }}
style={{
width: 150,
height: 80,
backgroundColor: 'tomato',
marginTop: 10,
borderRadius: 8,
}}
/>
)}
</AnimatePresence>
</>
);
}
5. 기초 애니메이션 구현
Framer Motion의 애니메이션은 기본적으로 다음 네 가지 속성을 조합해서 만듭니다:
- initial: 시작 상태
- animate: 최종 상태
- exit: 제거 시 상태
- transition: 애니메이션 전환 방식
5.1 페이드 인 / 아웃
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1 }}
/>
- opacity를 0 → 1로 점진적으로 변화시켜 등장 효과
- AnimatePresence와 함께 쓰면 exit도 자연스럽게 동작
5.2 슬라이드 (좌/우/상/하)
<motion.div
initial={{ x: -100 }} // 왼쪽 바깥에서 시작
animate={{ x: 0 }} // 가운데로 이동
transition={{ duration: 0.5 }}
/>
- x 또는 y 값을 변경해 슬라이딩
- 방향만 바꾸면 상하/좌우 이동 전부 구현 가능
5.3 스케일 (크기 변경)
<motion.div
initial={{ scale: 0.5 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3 }}
/>
- scale: 1은 원래 크기, 0.5는 절반
- 사용 시 요소가 중앙에서 커지는 느낌
5.4 회전 (Rotate)
<motion.div
initial={{ rotate: -180 }}
animate={{ rotate: 0 }}
transition={{ duration: 0.6 }}
/>
- rotate 단위는 deg(도) 단위
- 이미지, 아이콘 등을 자연스럽게 회전시킬 때 유용
6. transition 속성 이해
transition은 애니메이션의 속도, 지연 시간, 곡선 등을 설정합니다.
6.1 기본 형태
transition={{
duration: 0.5,
delay: 0.2,
ease: 'easeInOut'
}}
주요 속성:
속성 설명
| duration | 애니메이션이 완료되기까지 걸리는 시간 (초) |
| delay | 애니메이션 시작 전 대기 시간 |
| ease | 애니메이션의 속도 곡선 (easing function) |
6.2 ease 종류
| 'linear' | 일정한 속도 |
| 'easeIn' | 천천히 시작 |
| 'easeOut' | 천천히 끝남 |
| 'easeInOut' | 양쪽 모두 천천히 |
| [0.42, 0, 0.58, 1] | 커스텀 cubic bezier 곡선 |
⚠️ 참고: ease 값은 CSS transition-timing-function과 유사한 개념입니다.
6.3 스프링(Spring) 애니메이션
Framer Motion의 기본 전환 방식은 사실 spring입니다. 아래처럼 type: 'spring'으로 설정하면 물리 기반의 탄력 있는 모션이 생성됩니다.
transition={{
type: 'spring',
stiffness: 200, // 스프링 강도 (높을수록 빠르고 강하게)
damping: 20 // 마찰 계수 (낮을수록 튕김 많음)
}}
spring 속성 요약:
속성 의미
| type | 'spring' 또는 'tween' |
| stiffness | 스프링의 탄성 강도 |
| damping | 진동 마찰 계수 |
| mass | 질량 (더 무겁게 튕김) |
| bounce | 튕김 비율 |
| restDelta | 언제 정지로 간주할지 기준 |
spring은 UI에 자연스러운 반응을 주기 때문에 버튼 클릭, 등장/퇴장 등에서 많이 쓰입니다.
예제: 다양한 애니메이션 + 전환 방식 비교
<motion.div
initial={{ opacity: 0, scale: 0.5, rotate: -45 }}
animate={{ opacity: 1, scale: 1, rotate: 0 }}
transition={{
duration: 0.8,
ease: 'easeOut',
}}
>
기본 트윈 애니메이션
</motion.div>
<motion.div
initial={{ x: -200 }}
animate={{ x: 0 }}
transition={{
type: 'spring',
stiffness: 300,
damping: 15,
}}
>
스프링 애니메이션
</motion.div>
2단계: 상호작용 및 상태 기반 애니메이션 (중급)
1. 사용자 인터랙션 연결: whileHover, whileTap
Framer Motion은 기본적으로 마우스/터치에 반응하는 상호작용 애니메이션을 제공합니다.
주로 button, card, icon 컴포넌트에 활용됩니다.
예제: 버튼에 hover, tap 효과 주기
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95, rotate: -2 }}
transition={{ type: 'spring', stiffness: 300 }}
style={{
padding: '12px 24px',
background: '#007bff',
color: 'white',
borderRadius: 8,
border: 'none',
fontSize: 16,
cursor: 'pointer',
}}
>
Click Me
</motion.button>
주요 속성 설명
| whileHover | 마우스를 올렸을 때 애니메이션 실행 |
| whileTap | 클릭(또는 터치) 시 애니메이션 실행 |
💡 Tip: scale, rotate, opacity, boxShadow, color 등의 스타일과 함께 사용하면 매우 직관적이고 강력합니다.
2. 제스처 기반 애니메이션: Drag
Framer Motion은 drag 속성을 통해 컴포넌트를 드래그 가능하게 만들 수 있는 매우 강력한 기능을 제공합니다.
예제: 제한 없이 자유롭게 드래그
<motion.div
drag
dragMomentum={false}
whileDrag={{ scale: 1.1 }}
style={{
width: 120,
height: 120,
background: 'tomato',
borderRadius: 12,
cursor: 'grab',
}}
>
드래그 해보세요!
</motion.div>
예제: 드래그 제약 (dragConstraints)
const constraintsRef = useRef(null);
return (
<div
ref={constraintsRef}
style={{
width: 300,
height: 300,
border: '2px dashed gray',
position: 'relative',
marginTop: 20,
}}
>
<motion.div
drag
dragConstraints={constraintsRef}
style={{
width: 80,
height: 80,
background: '#00c853',
borderRadius: 12,
position: 'absolute',
}}
/>
</div>
);
- dragConstraints: 움직일 수 있는 범위를 정해줍니다.
- dragMomentum={false}: 드래그 해제 후 관성 이동 제거
- dragElastic={0.2}: 바깥쪽 끌림 허용 정도 (0 = 고정, 1 = 매우 유연)
3. 드래그 이벤트 핸들링: onDragStart, onDragEnd
애니메이션 외에도, 드래그 이벤트 감지 후 추가 로직 실행이 가능합니다.
<motion.div
drag
onDragStart={() => console.log('드래그 시작')}
onDragEnd={(e, info) => {
console.log('드래그 끝', info);
// info.offset.x, info.offset.y 등으로 위치 추적 가능
}}
style={{
width: 100,
height: 100,
background: '#6200ea',
borderRadius: 8,
}}
/>
onDragEnd 인자 설명
(e: MouseEvent, info: PanInfo) => void
- info.offset: 드래그된 거리 (x, y)
- info.velocity: 드래그 종료 순간 속도
이를 활용하면 드래그 후 위치 이동, 카드 스냅, 정렬 등 다양한 기능에 응용할 수 있습니다.
활용 예제: 카드 스냅 애니메이션 (기초)
const [x, setX] = useState(0);
<motion.div
drag="x"
dragConstraints={{ left: -100, right: 100 }}
onDragEnd={(e, info) => {
if (info.offset.x > 100) setX(200); // 오른쪽으로 스냅
else if (info.offset.x < -100) setX(-200); // 왼쪽으로 스냅
else setX(0); // 원래 자리로
}}
animate={{ x }}
style={{
width: 200,
height: 120,
background: '#00bcd4',
borderRadius: 10,
cursor: 'grab',
}}
>
Swipe me
</motion.div>
4. 상태 전환 애니메이션
4.1 React 상태 기반 애니메이션
Framer Motion은 animate에 state 기반 값을 주면, 상태가 바뀔 때마다 자동으로 애니메이션 됩니다.
const [isOpen, setIsOpen] = useState(false);
<motion.div
animate={{ height: isOpen ? 200 : 80 }}
transition={{ duration: 0.4 }}
>
콘텐츠 영역
</motion.div>
- 상태 변경에 따라 height가 자연스럽게 전환됩니다.
- 이 방식은 toggle UI, 사이드바, 상세정보 펼침 등에 자주 사용됩니다.
4.2 조건부 렌더링 + AnimatePresence
기본적으로 컴포넌트가 unmount되면 exit 애니메이션이 실행되지 않습니다.
이를 해결하려면 AnimatePresence로 감싸야 합니다.
import { AnimatePresence, motion } from 'framer-motion';
const [show, setShow] = useState(true);
<AnimatePresence>
{show && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.3 }}
>
안녕하세요
</motion.div>
)}
</AnimatePresence>
- initial: 마운트 직후 상태
- exit: 언마운트될 때 실행되는 애니메이션
- AnimatePresence는 반드시 해당 컴포넌트 상위에 위치해야 작동합니다.
5. Variants 사용법
5.1 개념
Variants는 여러 상태(initial, animate, exit 등)에 이름을 붙여서 애니메이션 정의를 재사용하는 방법입니다.
특히 여러 컴포넌트에 동일한 모션을 적용할 때 유용합니다.
5.2 기본 사용 예시
const boxVariants = {
hidden: { opacity: 0, y: -20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, y: 20 }
};
<motion.div
variants={boxVariants}
initial="hidden"
animate="visible"
exit="exit"
/>
- variants: 상태 정의 객체
- initial, animate, exit 값은 variants의 key를 참조
- 반복적으로 같은 애니메이션을 적용할 때 코드 중복을 줄여줍니다.
5.3 부모-자식 연계 (stagger 효과)
const container = {
visible: {
transition: {
staggerChildren: 0.1
}
}
};
const item = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
};
<motion.ul
variants={container}
initial="hidden"
animate="visible"
>
{[1, 2, 3].map(i => (
<motion.li key={i} variants={item}>
항목 {i}
</motion.li>
))}
</motion.ul>
- staggerChildren: 자식 애니메이션을 순차적으로 실행합니다.
6. 레이아웃 애니메이션
6.1 layout 속성
- 요소의 사이즈/위치가 변할 때 자동으로 부드러운 전환을 제공
- 상태 기반 UI에서 매우 강력한 기능
const [expanded, setExpanded] = useState(false);
<motion.div
layout
onClick={() => setExpanded(!expanded)}
style={{
width: 200,
height: expanded ? 300 : 100,
background: 'orange',
borderRadius: 12,
}}
>
클릭해서 크기 변경
</motion.div>
- layout 속성 하나만 넣어도 크기, 위치, 마진 등 변경에 자동 반응합니다.
- 내부 요소도 함께 layout을 사용하면 계층적 애니메이션 구현 가능
6.2 layoutId로 페이지 간 전환 구현
- layoutId는 두 컴포넌트가 같은 요소라고 인식시키는 식별자입니다.
- 페이지 간 이동 시 layoutId가 같으면 부드럽게 전환됩니다.
// 페이지 A
<Link to="/detail">
<motion.div layoutId="image" />
</Link>
// 페이지 B
<motion.div layoutId="image" />
- AnimatePresence와 함께 사용해야 자연스럽게 동작
- 예시: 리스트에서 카드 클릭 → 상세페이지로 부드럽게 확대
1. Scroll 기반 애니메이션
스크롤 위치에 따라 요소의 opacity, translate, scale 등을 자연스럽게 조절할 수 있습니다.
Framer Motion은 이를 위해 useScroll, useTransform 훅을 제공합니다.
1.1 핵심 Hook 소개
import { useScroll, useTransform, motion } from 'framer-motion';
| useScroll() | 스크롤 위치 (scrollY, scrollYProgress) 추적 |
| useTransform(input, output) | 입력 범위를 출력 범위로 선형 매핑 |
1.2 기본 예제: 스크롤에 따라 투명도 조절
import { motion, useScroll, useTransform } from 'framer-motion';
import { useRef } from 'react';
export default function ScrollFade() {
const ref = useRef(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] });
const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]);
return (
<div style={{ height: '200vh', padding: '100px' }}>
<motion.div
ref={ref}
style={{
opacity,
height: 200,
background: '#673ab7',
borderRadius: 20,
}}
>
스크롤에 따라 등장!
</motion.div>
</div>
);
}
설명
- ref: 관찰 대상 요소
- scrollYProgress: 대상 요소가 뷰포트에 들어올 때 0 → 1로 변화
- useTransform: 0
1 범위를 투명도 01로 매핑
1.3 패럴랙스 효과 (Parallax)
const y = useTransform(scrollYProgress, [0, 1], ['0px', '-100px']);
<motion.div style={{ y }}>
배경 이미지처럼 천천히 움직임
</motion.div>
- 배경은 느리게, 전경은 빠르게 움직이면 입체감이 생김
- y, scale, rotate를 조합해 다채로운 효과 구현 가능
2. SVG 및 경로 애니메이션
Framer Motion은 SVG의 pathLength, strokeDasharray 등의 속성도 애니메이션 처리할 수 있습니다.
2.1 로고 드로잉 애니메이션
<motion.svg
width="200"
height="200"
viewBox="0 0 200 200"
initial="hidden"
animate="visible"
>
<motion.path
d="M 20 100 Q 100 0 180 100"
stroke="#2196f3"
strokeWidth="4"
fill="transparent"
variants={{
hidden: { pathLength: 0 },
visible: {
pathLength: 1,
transition: {
duration: 2,
ease: 'easeInOut',
},
},
}}
/>
</motion.svg>
핵심 속성
속성 설명
| pathLength | 0~1로 선 그리기 제어 |
| strokeDasharray | 점선 스타일 |
| strokeDashoffset | 그리기 시작 위치 조절 |
💡 대부분의 "라인이 그려지는" 애니메이션은 pathLength 하나로 해결됩니다.
2.2 프로그레스 바 (원형)
const progress = useScroll().scrollYProgress;
const circumference = 2 * Math.PI * 50; // 반지름 50 기준
const strokeDashoffset = useTransform(progress, [0, 1], [circumference, 0]);
<motion.svg width={120} height={120}>
<circle
cx="60"
cy="60"
r="50"
stroke="#ccc"
strokeWidth="8"
fill="none"
/>
<motion.circle
cx="60"
cy="60"
r="50"
stroke="#00bcd4"
strokeWidth="8"
fill="none"
strokeDasharray={circumference}
style={{ strokeDashoffset }}
/>
</motion.svg>
- scrollYProgress가 0 → 1일 때 strokeDashoffset이 줄어들면서 원이 채워집니다.
- 마치 로딩 프로그레스 바처럼 연출 가능
3. 애니메이션 시퀀싱 / 타이밍 제어
3.1 staggerChildren, delayChildren
- 부모 컴포넌트의 transition 안에서 자식 컴포넌트들의 애니메이션 타이밍을 제어합니다.
- 요소들이 순차적으로 등장하는 효과를 구현할 수 있습니다.
const parentVariants = {
visible: {
transition: {
staggerChildren: 0.2,
delayChildren: 0.3,
},
},
};
const childVariants = {
hidden: { opacity: 0, y: 10 },
visible: { opacity: 1, y: 0 },
};
<motion.ul
initial="hidden"
animate="visible"
variants={parentVariants}
>
{[...Array(4)].map((_, i) => (
<motion.li key={i} variants={childVariants}>
항목 {i + 1}
</motion.li>
))}
</motion.ul>
| staggerChildren | 자식 간 애니메이션 간격 |
| delayChildren | 전체 자식 애니메이션 시작 지연 |
3.2 useAnimation + controls.start()
- 수동으로 애니메이션을 시작, 정지, 제어할 수 있는 훅입니다.
- 버튼 클릭, API 응답 등에 반응하여 트리거 가능한 구조입니다.
import { useAnimation, motion } from 'framer-motion';
const controls = useAnimation();
const handleClick = () => {
controls.start({
x: 100,
opacity: 1,
transition: { duration: 0.5 },
});
};
<motion.div
animate={controls}
initial={{ opacity: 0 }}
style={{ width: 100, height: 100, background: 'salmon' }}
/>
<button onClick={handleClick}>Animate</button>
4. React Hook과 연계
4.1 useCycle: 상태 순환
- 여러 애니메이션 상태를 순서대로 순환할 수 있습니다.
import { useCycle } from 'framer-motion';
const [x, cycleX] = useCycle(0, 100, -100);
<motion.div animate={{ x }} />
<button onClick={() => cycleX()}>다음 위치</button>
- 클릭할 때마다 0 → 100 → -100 → 0 순으로 반복
- 토글형 UI, 슬라이더, 툴팁 등에서 유용
4.2 useInView: 뷰포트 진입 시 애니메이션 실행
import { useInView, motion } from 'framer-motion';
import { useRef } from 'react';
const ref = useRef(null);
const isInView = useInView(ref, { once: true }); // 한 번만 실행
<motion.div
ref={ref}
initial={{ opacity: 0, y: 30 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5 }}
>
스크롤로 나타나는 컴포넌트
</motion.div>
- 페이지 구성요소를 지연 등장시킬 때 가장 많이 쓰는 패턴입니다.
- threshold, margin 등을 설정해 정밀 조절 가능
5. 맞춤 애니메이션 작성
5.1 커스텀 ease 함수 정의
- ease 속성에 cubic bezier 배열을 직접 정의할 수 있습니다.
<motion.div
animate={{ x: 200 }}
transition={{
duration: 0.8,
ease: [0.6, -0.05, 0.01, 0.99], // 커스텀 ease (빠르게 시작, 천천히 끝)
}}
/>
- 커스텀 곡선을 쓰면 UX가 훨씬 부드럽고 개성 있게 느껴집니다.
5.2 motion.custom → 최신 Framer Motion에서는 styled-components 통합 방법 변경됨
예전 방식: motion.custom(MyStyledComponent) → deprecated
권장 방식 (with styled-components):
import styled from 'styled-components';
import { motion } from 'framer-motion';
const StyledBox = styled(motion.div)`
width: 120px;
height: 120px;
background: #2196f3;
border-radius: 12px;
`;
<StyledBox
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring' }}
/>
- motion의 모든 기능을 유지하면서 styled-components와 통합 가능
- CSS-in-JS 환경에서 프리셋 스타일 + 애니메이션 분리 가능
4단계: 프로젝트 실전 적용
1. 모달 열고 닫기 애니메이션
import { motion, AnimatePresence } from 'framer-motion';
function Modal({ isOpen, onClose }) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.6)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ duration: 0.3 }}
style={{ background: 'white', padding: 20, borderRadius: 8 }}
>
모달 내용입니다
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
- AnimatePresence로 모달 exit 애니메이션 적용
- 외부 클릭 시 onClose로 상태 전환
2. 카드 리스트 등장 / 이동 / 삭제
import { AnimatePresence, motion } from 'framer-motion';
function CardList({ items }) {
return (
<AnimatePresence>
{items.map((item) => (
<motion.div
key={item.id}
layout
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
style={{
margin: '10px 0',
padding: 16,
border: '1px solid #ccc',
borderRadius: 8,
}}
>
{item.name}
</motion.div>
))}
</AnimatePresence>
);
}
- layout을 사용하면 DOM 순서가 바뀌어도 부드럽게 이동
- key는 반드시 고유해야 함
3. 페이지 전환 시 레이아웃 이동 (layoutId 활용)
A 페이지
<Link to={`/detail/${id}`}>
<motion.img src={imgUrl} layoutId={`image-${id}`} />
</Link>
B 페이지 (상세 화면)
<motion.img src={imgUrl} layoutId={`image-${id}`} />
- 두 컴포넌트 모두 같은 layoutId를 사용하면 자동으로 부드러운 전환이 적용됨
- 레이아웃 위치, 사이즈, 투명도 등이 자연스럽게 이동
4. Next.js 환경에서의 사용법
4.1 AnimatePresence + 페이지 전환
Next.js에서는 페이지가 사라질 때 자동으로 DOM이 제거되기 때문에 AnimatePresence가 작동하지 않습니다.
이를 해결하기 위해 _app.tsx에서 페이지를 수동으로 애니메이션 처리합니다.
예시 (_app.tsx)
import { AnimatePresence } from 'framer-motion';
import { useRouter } from 'next/router';
export default function App({ Component, pageProps }) {
const router = useRouter();
return (
<AnimatePresence mode="wait" initial={false}>
<Component {...pageProps} key={router.asPath} />
</AnimatePresence>
);
}
- key={router.asPath}: 페이지 전환 시 exit/enter 효과 적용을 위해 반드시 필요
- mode="wait": 이전 페이지 애니메이션이 끝날 때까지 다음 페이지 렌더링 대기
4.2 SSR + Framer Motion 호환성
- Framer Motion은 클라이언트 전용 코드입니다.
- Next.js에서는 SSR 시 DOM 관련 동작을 지연시켜야 오류가 없습니다.
해결 방법:
import dynamic from 'next/dynamic';
const MotionComponent = dynamic(() => import('./MotionComponent'), { ssr: false });
5. UI/UX 향상 적용 전략
5.1 접근성과 애니메이션의 균형
- motion.div는 시멘틱 HTML이 아닙니다. 가능하면 motion.button, motion.main 등으로 대체
- 움직이는 요소는 aria-hidden 처리가 필요한 경우가 많음
- prefers-reduced-motion 사용 권장:
import { useReducedMotion } from 'framer-motion';
const shouldReduce = useReducedMotion();
<motion.div
animate={shouldReduce ? {} : { opacity: 1, y: 0 }}
/>
시스템 설정에서 애니메이션을 끈 사용자에 대한 고려
6. Framer Motion vs GSAP 실무 비교
항목 | Framer Motion | GSAP
| React 통합 | 매우 우수 (컴포넌트 기반) | 별도 관리 필요 |
| 선언형 | ✅ | ❌ (명령형) |
| 커스터마이징 | 보통 | 매우 세밀함 |
| 제어력 | useAnimation 한정 | timeline, scrollTrigger, 완전 제어 |
| 복잡한 시퀀스 | 제한적 (variants) | 강력 (GSAP timeline) |
| 러닝커브 | 낮음 (React 친화적) | 중간~높음 |
| 스크롤 애니메이션 | 기본 가능 | 강력 (scrollTrigger) |
결론:
- UI 인터랙션, 페이지 전환, 간단한 트랜지션 → Framer Motion 추천
- 정밀한 스크롤 시퀀스, 타이밍 제어 → GSAP (특히 ScrollTrigger)
디버깅 툴 사용: motion-devtools
개요
motion-devtools는 Framer Motion의 실험적 디버깅 도구로, 현재 애니메이션 상태와 motion 컴포넌트의 실시간 props 변화를 시각화해줍니다.
설치 및 사용
npm install motion-devtools
import { useEffect } from 'react';
import { animate, timeline } from 'framer-motion';
import { useMotionDevTools } from 'motion-devtools';
export default function App() {
useMotionDevTools(); // Devtools 활성화
return (
<motion.div animate={{ x: 100 }} />
);
}
기능
- 현재 컴포넌트들의 animate, initial, exit 상태 추적
- 실제 애니메이션 값 (x, y, opacity, transform) 로그 확인
- useAnimation()으로 동적으로 제어되는 값도 추적 가능
⚠️ 주의: 실험적 기능이며, 모든 브라우저에서 완벽히 지원되진 않습니다.
성능 고려: transform vs top/left 애니메이션
핵심 비교
속성 | GPU 사용 | 레이아웃 리플로우 | 성능
| transform: translateX() | ✅ | ❌ | 좋음 |
| top / left | ❌ | ✅ | 느림 |
왜 transform이 빠른가?
- transform은 GPU 가속 레이어에서 처리되므로 repaint 없이 동작합니다.
- 반면 top, left는 브라우저의 layout/reflow 단계를 유발합니다.
→ 성능 저하 및 불안정한 프레임 발생
권장사항
- x, y → transform 기반 → 애니메이션 시 필수
- top, left, width, height → 상태 변화에는 사용 가능하지만 애니메이션에는 피할 것
<motion.div animate={{ x: 100 }} /> // ✅ transform
<motion.div animate={{ left: 100 }} /> // ❌ reflow 발생
디자인 협업: Figma → Framer Motion 연계 포인트
Figma 기준 속성 vs Framer 속성 비교
Figma 속성 | Framer Motion 속성 | 설명
| Opacity | opacity | 동일 |
| Position X/Y | x, y | transform 기준 |
| Scale | scale | 기본값 1 |
| Rotation | rotate | 단위 deg |
| Ease In/Out | transition.ease | easeIn, easeOut, 커스텀 bezier |
| Delay | transition.delay | 초 단위로 설정 |
디자인 → 개발 시 고려 사항
- Easing 매핑
- Figma에서 사용하는 Ease In, Ease Out은 Framer에서 다음과 매핑됩니다:
- Ease In → easeIn
- Ease Out → easeOut
- Custom → [0.25, 0.1, 0.25, 1] (직접 정의)
- Figma에서 사용하는 Ease In, Ease Out은 Framer에서 다음과 매핑됩니다:
- 시간 단위 확인
- Figma는 ms, Framer는 초 단위 (0.3, 0.5 등)
- 레이어 순서 / 상태 전이 정의
- Variants로 구성할 수 있도록 상태 정의서(예: Hover → Active → Disabled)를 명확히 분리
- 디자이너와 협업 포인트
- 애니메이션 정의 문서에 다음 포함:
- 등장 효과 (initial → animate)
- 인터랙션 (hover/tap)
- 페이지 전환 방식 (layoutId 필요 여부)
- 사용자 접근성 고려 (reduce motion, 빠른 타이밍은 피하기)
- 애니메이션 정의 문서에 다음 포함:
'Graphic > lib' 카테고리의 다른 글
| glTF 성능 최적화(Draco 메시 압축 + KTX2 텍스처 + Three.js 로더 설정) (0) | 2025.11.18 |
|---|---|
| GSAP 가이드 (2) | 2025.09.04 |
| maath (0) | 2025.06.09 |
| React Three Rapier (3) | 2025.05.27 |
| GSAP (GreenSock Animation Platform) (0) | 2025.03.07 |