🌐 웹개발

Framer Motion

프레이머 모션

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>
  );
}

🗣️ 실무 대화 예시

👨‍🎨 UX 디자이너
"이 리스트 아이템들이 순차적으로 fade-in 되면서 살짝 위로 올라오는 애니메이션이면 좋겠어요. 그리고 삭제할 때는 옆으로 슬라이드되면서 사라지게요."
👨‍💻 프론트엔드 개발자
"Framer Motion이면 금방 돼요. variants로 staggerChildren 설정하면 순차 애니메이션이고, AnimatePresence로 exit 애니메이션 넣으면 됩니다. layout prop도 추가하면 삭제 후 나머지 아이템들이 자연스럽게 재배치돼요."
👩‍💻 테크 리드
"애니메이션 라이브러리 번들 사이즈가 걱정인데, Framer Motion 괜찮아요?"
👨‍💻 프론트엔드 개발자
"기본 번들이 약 40KB gzip인데, tree-shaking이 잘 되어서 실제로는 적게 들어갑니다. motion/react 경량 버전도 있고요. 저희처럼 인터랙션이 많은 UI라면 충분히 가치 있어요. CSS만으로는 exit 애니메이션이나 레이아웃 전환이 너무 복잡하거든요."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기