본문 바로가기
Graphic/lib

motion(framer-motion)

by curious week 2025. 6. 9.

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: 01 범위를 투명도 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 초 단위로 설정

디자인 → 개발 시 고려 사항

  1. Easing 매핑
    • Figma에서 사용하는 Ease In, Ease Out은 Framer에서 다음과 매핑됩니다:
      • Ease In → easeIn
      • Ease Out → easeOut
      • Custom → [0.25, 0.1, 0.25, 1] (직접 정의)
  2. 시간 단위 확인
    • Figma는 ms, Framer는 초 단위 (0.3, 0.5 등)
  3. 레이어 순서 / 상태 전이 정의
    • Variants로 구성할 수 있도록 상태 정의서(예: Hover → Active → Disabled)를 명확히 분리
  4. 디자이너와 협업 포인트
    • 애니메이션 정의 문서에 다음 포함:
      • 등장 효과 (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