콘텐츠 대표 이미지 - JavaScript 애니메이션의 모든 것 — GSAP & Framer Motion 완전 정복 가이드 🚀
🎬 JavaScript Animation

JavaScript 애니메이션의 모든 것 — GSAP & Framer Motion 완전 정복 가이드 🚀

바닐라 JS부터 최강 라이브러리까지, 웹 애니메이션의 세계를 같이 탐험해보자!

안녕! 오늘은 웹 개발자라면 한 번쯤은 꼭 마주치게 되는 그 주제, 바로 JavaScript 애니메이션에 대해 진짜 깊이 파고들어볼 거야. 😄

"애니메이션? 그냥 CSS로 하면 되는 거 아니야?" 라고 생각할 수도 있는데, 실제로 복잡한 인터랙션이나 시퀀스 애니메이션, 물리 기반 움직임 같은 걸 구현하다 보면 CSS만으로는 한계가 느껴지거든. 그때 등장하는 게 바로 GSAP과 Framer Motion 같은 강력한 라이브러리들이야!

이 글에서는 바닐라 JavaScript 애니메이션의 기초부터 시작해서, 업계에서 가장 많이 쓰이는 두 라이브러리의 핵심 개념과 실전 활용법까지 싹 다 정리해줄게. 커피 한 잔 들고 편하게 읽어봐! ☕


웹 애니메이션 기술 스택 한눈에 보기 🟨 바닐라 JavaScript requestAnimationFrame Web Animations API CSS 트랜지션 제어 ✔ 의존성 없음 ✔ 가볍고 빠름 △ 복잡한 시퀀스 어려움 난이도 ★★★☆☆ 💚 GSAP 타임라인 기반 제어 ScrollTrigger 플러그인 이징·모핑·SVG 지원 ✔ 강력한 시퀀스 제어 ✔ 크로스브라우저 완벽 △ 상업용 유료 플러그인 난이도 ★★★★☆ ⚛️ Framer Motion React 전용 라이브러리 선언형 애니메이션 제스처·레이아웃 지원 ✔ React와 완벽 통합 ✔ 직관적인 API △ React 전용 난이도 ★★★☆☆ → → 더 강력하게 React 특화 프로젝트 규모와 기술 스택에 따라 적합한 도구를 선택하는 것이 핵심!

1. 바닐라 JavaScript 애니메이션 기초 🌱

라이브러리 없이 순수 JavaScript만으로 애니메이션을 구현하는 방법부터 알아보자. 이걸 이해해야 나중에 라이브러리가 왜 편한지도 체감이 되거든!

1-1. requestAnimationFrame — 애니메이션의 심장 박동 💓

예전에는 setInterval이나 setTimeout으로 애니메이션을 구현했는데, 이 방법은 브라우저의 렌더링 사이클과 동기화가 안 돼서 버벅임이 생기거나 배터리를 낭비하는 문제가 있었어.

그래서 등장한 게 바로 requestAnimationFrame(rAF)이야. 브라우저가 다음 화면을 그리기 직전에 콜백 함수를 호출해줘서, 보통 초당 60프레임(60fps)으로 부드럽게 동작해.

// requestAnimationFrame 기본 사용법
let startTime = null;
const duration = 1000; // 1초 동안 애니메이션

function animate(timestamp) {
  if (!startTime) startTime = timestamp;
  const elapsed = timestamp - startTime;
  const progress = Math.min(elapsed / duration, 1); // 0 ~ 1 사이 값

  // 요소를 0px에서 300px까지 이동
  const element = document.getElementById('box');
  element.style.transform = `translateX(${progress * 300}px)`;

  if (progress < 1) {
    requestAnimationFrame(animate); // 아직 안 끝났으면 다음 프레임 요청
  }
}

requestAnimationFrame(animate); // 시작!
📌 rAF의 핵심 특징 • 브라우저 탭이 비활성화되면 자동으로 일시정지 → 배터리 절약 🔋
• 모니터 주사율에 맞춰 자동 최적화 (60Hz → 60fps, 120Hz → 120fps)
• cancelAnimationFrame(id)로 취소 가능
• timestamp 파라미터로 정확한 시간 기반 애니메이션 구현 가능

1-2. 이징(Easing) 함수 — 자연스러운 움직임의 비밀 🎭

선형(linear)으로 움직이는 애니메이션은 로봇처럼 딱딱해 보여. 실제 물체는 가속하고 감속하잖아? 그걸 수학적으로 표현한 게 이징 함수야.

// 자주 쓰이는 이징 함수들
const easing = {
  // 선형 (딱딱함)
  linear: t => t,

  // 천천히 시작해서 빠르게 끝남
  easeIn: t => t * t,

  // 빠르게 시작해서 천천히 끝남
  easeOut: t => t * (2 - t),

  // 천천히 시작, 빠르게 중간, 천천히 끝
  easeInOut: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,

  // 탄성 효과 (살짝 튕기는 느낌)
  elastic: t => {
    const c4 = (2 * Math.PI) / 3;
    return t === 0 ? 0 : t === 1 ? 1
      : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
  }
};

// 사용 예시
function animate(timestamp) {
  const progress = elapsed / duration;
  const easedProgress = easing.easeInOut(progress); // 이징 적용!
  element.style.transform = `translateX(${easedProgress * 300}px)`;
}

1-3. Web Animations API (WAAPI) — 현대적인 네이티브 방법 ✨

2015년부터 표준화된 Web Animations API는 CSS 애니메이션의 성능과 JavaScript의 제어력을 동시에 가져다줘. 현재 모든 모던 브라우저에서 지원해!

// Web Animations API 사용법
const element = document.getElementById('box');

// animate() 메서드로 애니메이션 생성
const animation = element.animate(
  // 키프레임 배열
  [
    { transform: 'translateX(0px)', opacity: 1 },
    { transform: 'translateX(300px)', opacity: 0.5 },
    { transform: 'translateX(0px)', opacity: 1 }
  ],
  // 옵션
  {
    duration: 2000,      // 2초
    iterations: Infinity, // 무한 반복
    easing: 'ease-in-out',
    fill: 'forwards'     // 끝난 후 마지막 상태 유지
  }
);

// 제어 메서드들
animation.pause();   // 일시정지
animation.play();    // 재생
animation.reverse(); // 역방향 재생
animation.cancel();  // 취소

// 완료 이벤트
animation.onfinish = () => console.log('애니메이션 완료! 🎉');
💡 WAAPI vs CSS Animation 차이점 CSS 애니메이션은 선언적이라 동적 값 변경이 어렵지만, WAAPI는 JavaScript로 런타임에 값을 계산해서 적용할 수 있어. 특히 사용자 입력에 반응하는 애니메이션에 강점이 있어!

2. GSAP — 프로 개발자들의 애니메이션 무기 💚

GSAP(GreenSock Animation Platform)은 2008년부터 시작된 역사 깊은 JavaScript 애니메이션 라이브러리야. 지금도 업계 표준으로 불릴 만큼 강력하고, 넷플릭스, 구글, 애플 같은 대기업들도 사용하는 검증된 도구야. 🏆

2023년 기준으로 GSAP 3.x 버전이 최신이고, 핵심 기능은 무료로 사용할 수 있어. (일부 고급 플러그인은 클럽 멤버십 필요)

GSAP 핵심 구조 한눈에 보기 GSAP GreenSock Animation Platform gsap.to() 현재 → 목표값으로 가장 많이 쓰임 gsap.from() 시작값 → 현재로 등장 효과에 유용 gsap.fromTo() 시작값 → 끝값 완전한 제어 Timeline 시퀀스 애니메이션 순서 제어의 핵심 ScrollTrigger 스크롤 연동 애니메이션 가장 인기 플러그인 Stagger 여러 요소 순차 실행 리스트 등장 효과 모든 기능이 GSAP 코어를 중심으로 유기적으로 연결됨

2-1. GSAP 설치 및 기본 설정 🔧

// npm으로 설치
npm install gsap

// 또는 CDN으로 바로 사용
// <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

// ES Module import
import { gsap } from "gsap";

// 플러그인 import (필요한 것만)
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { TextPlugin } from "gsap/TextPlugin";

// 플러그인 등록 (반드시 해야 함!)
gsap.registerPlugin(ScrollTrigger, TextPlugin);

2-2. gsap.to() / from() / fromTo() — 기본 트윈 🎯

GSAP에서 가장 기본이 되는 건 트윈(Tween)이야. 하나의 애니메이션 단위를 말하는데, 세 가지 주요 메서드가 있어.

// gsap.to() - 현재 상태에서 목표 상태로
gsap.to(".box", {
  x: 300,           // translateX(300px)
  y: 100,           // translateY(100px)
  rotation: 360,    // 360도 회전
  scale: 1.5,       // 1.5배 크기
  opacity: 0.5,     // 투명도 50%
  duration: 1.5,    // 1.5초
  ease: "power2.out", // 이징
  delay: 0.5,       // 0.5초 후 시작
  repeat: 2,        // 2번 반복 (-1이면 무한)
  yoyo: true,       // 왕복 애니메이션
});

// gsap.from() - 지정한 상태에서 현재 상태로 (등장 효과에 최적!)
gsap.from(".card", {
  y: 50,
  opacity: 0,
  duration: 0.8,
  ease: "back.out(1.7)", // 살짝 튕기는 효과
});

// gsap.fromTo() - 시작값과 끝값 모두 지정
gsap.fromTo(".element",
  { x: -200, opacity: 0 },  // 시작 상태
  { x: 0, opacity: 1, duration: 1 }  // 끝 상태
);

// 콜백 함수들
gsap.to(".box", {
  x: 300,
  duration: 1,
  onStart: () => console.log("시작!"),
  onComplete: () => console.log("완료!"),
  onUpdate: () => console.log("진행 중..."),
});
⚠️ 주의! GSAP에서 x, y는 transform: translate를 사용하고, left, top은 실제 CSS 속성을 변경해. 성능을 위해 가능하면 x, y를 사용하는 게 좋아!

2-3. Timeline — 시퀀스 애니메이션의 핵심 ⏱️

여러 애니메이션을 순서대로 실행하거나 겹치게 하고 싶을 때 Timeline을 써. 이게 GSAP의 진짜 강점이야!

// 타임라인 생성
const tl = gsap.timeline({
  defaults: { duration: 0.8, ease: "power2.out" }, // 공통 기본값
  repeat: -1,    // 전체 타임라인 무한 반복
  yoyo: true,    // 왕복
  paused: true,  // 처음엔 멈춰있음
});

// 순차적으로 애니메이션 추가
tl.from(".title", { y: -50, opacity: 0 })
  .from(".subtitle", { y: 30, opacity: 0 })  // 이전 것 끝나고 시작
  .from(".button", { scale: 0, opacity: 0 }, "-=0.3") // 0.3초 겹쳐서 시작
  .from(".image", { x: 100, opacity: 0 }, "<") // 이전 것과 동시에 시작
  .to(".highlight", { backgroundColor: "#ffff00" }, "+=0.5"); // 0.5초 후 시작

// 타임라인 제어
tl.play();
tl.pause();
tl.reverse();
tl.seek(1.5);    // 1.5초 지점으로 이동
tl.progress(0.5); // 50% 지점으로 이동

// 레이블 사용 (특정 지점에 이름 붙이기)
tl.addLabel("intro", 0)
  .addLabel("main", 2)
  .addLabel("outro", 4);

tl.play("main"); // "main" 레이블부터 재생

2-4. Stagger — 여러 요소 순차 등장 🌊

리스트 아이템이나 카드들이 하나씩 순차적으로 등장하는 효과, 많이 봤지? 그게 바로 Stagger야!

// 기본 stagger - 0.1초 간격으로 순차 등장
gsap.from(".card", {
  y: 50,
  opacity: 0,
  duration: 0.6,
  stagger: 0.1, // 각 요소 사이 0.1초 딜레이
});

// 고급 stagger 설정
gsap.from(".grid-item", {
  scale: 0,
  opacity: 0,
  duration: 0.5,
  stagger: {
    amount: 1.5,    // 전체 stagger 시간
    from: "center", // 중앙에서 바깥으로 (start/end/center/edges/random)
    grid: [3, 4],   // 3행 4열 그리드
    axis: "x",      // x축 방향으로만
  }
});

2-5. ScrollTrigger — 스크롤 연동 애니메이션 🖱️

요즘 웹사이트에서 스크롤하면 요소들이 나타나거나 움직이는 효과 많이 보이지? 그게 대부분 ScrollTrigger 플러그인으로 만든 거야. GSAP에서 가장 인기 있는 플러그인이기도 해!

import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);

// 기본 스크롤 트리거
gsap.from(".section", {
  y: 100,
  opacity: 0,
  duration: 1,
  scrollTrigger: {
    trigger: ".section",    // 트리거 요소
    start: "top 80%",       // 요소 상단이 뷰포트 80% 지점에 닿으면 시작
    end: "bottom 20%",      // 요소 하단이 뷰포트 20% 지점에 닿으면 끝
    toggleActions: "play none none reverse",
    // 각각: onEnter, onLeave, onEnterBack, onLeaveBack
    markers: true,          // 개발 중 디버깅용 마커 표시
  }
});

// 스크롤에 따라 실시간으로 움직이는 효과 (scrub)
gsap.to(".parallax-bg", {
  y: -200,
  scrollTrigger: {
    trigger: ".hero",
    start: "top top",
    end: "bottom top",
    scrub: 1, // 스크롤에 부드럽게 연동 (숫자 = 지연 시간)
  }
});

// 핀(pin) - 스크롤해도 요소가 고정되는 효과
gsap.to(".progress-bar", {
  width: "100%",
  scrollTrigger: {
    trigger: ".content",
    start: "top top",
    end: "bottom bottom",
    scrub: true,
    pin: ".sidebar", // 사이드바를 화면에 고정
  }
});
🌟 ScrollTrigger toggleActions 옵션 설명 toggleActions: "play pause resume reset"

순서대로 onEnter / onLeave / onEnterBack / onLeaveBack 이벤트에 대한 동작이야.

사용 가능한 값: play, pause, resume, reset, restart, complete, reverse, none

2-6. GSAP 이징 함수 종류 📈

GSAP은 정말 다양한 이징 함수를 제공해. 각각 느낌이 다르니까 상황에 맞게 골라 써봐!

이징 이름 특징 사용 예시
power1~4 기본 가속/감속 (숫자 클수록 강함) 일반적인 UI 전환
back.out 목표값을 살짝 넘었다가 돌아옴 버튼 등장, 팝업
elastic 탄성 있게 튕기는 효과 게임 UI, 재미있는 효과
bounce 공이 튀는 것처럼 바운스 알림, 강조 효과
expo 지수함수적 가속/감속 (극적인 효과) 히어로 섹션, 임팩트
circ 원형 곡선 기반 부드러운 전환
sine 사인 곡선 기반 (가장 부드러움) 미묘한 호버 효과
steps(n) n단계로 끊어서 이동 스프라이트 애니메이션
💡 GSAP 공식 사이트의 Ease Visualizer(gsap.com/docs/v3/Eases)에서 각 이징 함수의 그래프와 미리보기를 확인할 수 있어. 꼭 한번 들어가봐!

3. Framer Motion — React 개발자의 최애 라이브러리 ⚛️

Framer Motion은 React 전용 애니메이션 라이브러리야. 2019년에 Framer 팀에서 만들었고, 현재 버전 11.x까지 나왔어. React 생태계에서 가장 인기 있는 애니메이션 라이브러리 중 하나야.

GSAP이 "명령형(Imperative)" 방식이라면, Framer Motion은 선언형(Declarative) 방식이야. React의 철학과 딱 맞아서 코드가 훨씬 직관적이고 읽기 쉬워!

Framer Motion 핵심 개념 구조도 motion 컴포넌트 motion.div motion.span 모든 HTML 태그 지원 animate prop 목표 애니메이션 상태 객체 또는 variants 동적 변경 가능 variants 이름 있는 상태 정의 부모→자식 전파 재사용성 높음 useAnimation 명령형 제어 훅 start / stop 외부에서 트리거 제스처 지원 whileHover whileTap whileDrag / whileFocus Layout 애니메이션 layout prop 크기/위치 자동 감지 FLIP 기법 자동 적용 React 컴포넌트 기반으로 모든 기능이 자연스럽게 통합됨

3-1. 설치 및 기본 사용법 🚀

// 설치
npm install framer-motion

// 기본 import
import { motion } from "framer-motion";

// 가장 기본적인 사용법 - motion.div로 감싸기만 하면 됨!
function App() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}  // 초기 상태
      animate={{ opacity: 1, y: 0 }}   // 목표 상태
      transition={{ duration: 0.5 }}   // 전환 설정
    >
      안녕하세요! 🎉
    </motion.div>
  );
}
📌 motion 컴포넌트란? Framer Motion의 핵심은 motion 객체야. motion.div, motion.span, motion.button 등 모든 HTML 요소에 대응하는 motion 버전이 있어. 기존 HTML 태그 앞에 motion.만 붙이면 애니메이션 기능이 추가돼!

3-2. transition 옵션 상세 설정 ⚙️

// transition 다양한 설정
<motion.div
  animate={{ x: 100 }}
  transition={{
    // 기본 타입
    type: "tween",      // 일반 트윈 (기본값)
    duration: 0.5,
    ease: "easeInOut",

    // 또는 스프링 물리 효과
    type: "spring",
    stiffness: 300,    // 스프링 강도 (높을수록 빠름)
    damping: 20,       // 감쇠 (낮을수록 많이 튕김)
    mass: 1,           // 질량
    bounce: 0.25,      // 바운스 양

    // 또는 관성 효과
    type: "inertia",
    velocity: 200,     // 초기 속도

    // 공통 옵션
    delay: 0.2,        // 딜레이
    repeat: Infinity,  // 반복
    repeatType: "reverse", // 반복 타입 (loop/reverse/mirror)
    repeatDelay: 0.5,  // 반복 사이 딜레이
  }}
/>

3-3. variants — 재사용 가능한 애니메이션 상태 정의 📦

variants는 Framer Motion에서 가장 강력한 기능 중 하나야. 애니메이션 상태에 이름을 붙여서 재사용하고, 부모에서 자식으로 자동으로 전파할 수 있어!

// variants 정의
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1, // 자식들 0.1초 간격으로 순차 등장
      delayChildren: 0.3,   // 자식들 시작 전 0.3초 딜레이
    }
  }
};

const itemVariants = {
  hidden: { y: 20, opacity: 0 },
  visible: {
    y: 0,
    opacity: 1,
    transition: { type: "spring", stiffness: 300 }
  }
};

// 컴포넌트에서 사용
function CardList() {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
    >
      {items.map(item => (
        <motion.li
          key={item.id}
          variants={itemVariants}
          // initial, animate를 따로 안 써도 부모에서 자동 전파!
        >
          {item.text}
        </motion.li>
      ))}
    </motion.ul>
  );
}

3-4. 제스처 애니메이션 — 인터랙션의 꽃 🌸

Framer Motion은 마우스 호버, 클릭, 드래그 같은 제스처에 반응하는 애니메이션을 정말 쉽게 구현할 수 있어!

// 제스처 애니메이션
<motion.button
  // 호버 시 애니메이션
  whileHover={{
    scale: 1.05,
    backgroundColor: "#7bb8f0",
    boxShadow: "0 10px 30px rgba(0,0,0,0.2)"
  }}
  // 클릭(탭) 시 애니메이션
  whileTap={{ scale: 0.95 }}
  // 포커스 시 애니메이션
  whileFocus={{ outline: "2px solid #7bb8f0" }}
  transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
  클릭해봐! 🎯
</motion.button>

// 드래그 기능
<motion.div
  drag                    // 드래그 활성화
  dragConstraints={{      // 드래그 범위 제한
    top: -50,
    left: -50,
    right: 50,
    bottom: 50
  }}
  dragElastic={0.2}       // 탄성 (0~1)
  dragMomentum={true}     // 드래그 후 관성
  whileDrag={{ scale: 1.1, cursor: "grabbing" }}
>
  드래그해봐! 🖱️
</motion.div>

3-5. AnimatePresence — 마운트/언마운트 애니메이션 👋

React에서 컴포넌트가 DOM에서 제거될 때 애니메이션을 주는 건 원래 까다로운 작업이야. 근데 Framer Motion의 AnimatePresence를 쓰면 정말 간단해져!

import { motion, AnimatePresence } from "framer-motion";

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, y: 20 }}
            animate={{ scale: 1, opacity: 1, y: 0 }}
            exit={{ scale: 0.8, opacity: 0, y: 20 }}
            transition={{ type: "spring", damping: 25, stiffness: 300 }}
            onClick={e => e.stopPropagation()}
          >
            모달 내용 🎊
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

// 리스트 아이템 추가/제거 애니메이션
function TodoList({ items }) {
  return (
    <AnimatePresence>
      {items.map(item => (
        <motion.div
          key={item.id}
          initial={{ opacity: 0, height: 0 }}
          animate={{ opacity: 1, height: "auto" }}
          exit={{ opacity: 0, height: 0 }}
          transition={{ duration: 0.3 }}
        >
          {item.text}
        </motion.div>
      ))}
    </AnimatePresence>
  );
}

3-6. useScroll & useTransform — 스크롤 기반 애니메이션 📜

Framer Motion에서도 스크롤 연동 애니메이션을 구현할 수 있어. useScroll과 useTransform 훅을 조합하면 돼!

import { motion, useScroll, useTransform, useSpring } from "framer-motion";

function ParallaxSection() {
  const { scrollYProgress } = useScroll(); // 전체 페이지 스크롤 진행도 (0~1)

  // 스크롤 값을 다른 값으로 변환
  const y = useTransform(scrollYProgress, [0, 1], [0, -300]);
  const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
  const scale = useTransform(scrollYProgress, [0, 0.3], [1, 1.2]);

  // 스프링으로 부드럽게 만들기
  const smoothY = useSpring(y, { stiffness: 100, damping: 30 });

  return (
    <motion.div style={{ y: smoothY, opacity, scale }}>
      패럴랙스 효과! 🌊
    </motion.div>
  );
}

// 특정 요소 기준 스크롤
function SectionAnimation() {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start end", "end start"] // 요소 기준 시작/끝 지점
  });

  const x = useTransform(scrollYProgress, [0, 1], [-100, 100]);

  return (
    <div ref={ref}>
      <motion.div style={{ x }}>
        스크롤하면 움직여요! 🎯
      </motion.div>
    </div>
  );
}

3-7. Layout 애니메이션 — FLIP의 마법 🪄

Layout 애니메이션은 Framer Motion의 독특한 기능이야. 요소의 크기나 위치가 바뀔 때 자동으로 부드럽게 전환해줘. 내부적으로 FLIP(First, Last, Invert, Play) 기법을 사용해서 성능도 좋아!

// layout prop 하나만 추가하면 크기/위치 변화가 자동으로 애니메이션됨!
function ExpandableCard({ isExpanded, onClick }) {
  return (
    <motion.div
      layout  // 이거 하나로 레이아웃 변화 자동 애니메이션!
      onClick={onClick}
      style={{
        width: isExpanded ? 300 : 150,
        height: isExpanded ? 200 : 100,
      }}
    >
      <motion.h2 layout="position">제목</motion.h2>
      {isExpanded && (
        <motion.p
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
        >
          펼쳐진 내용...
        </motion.p>
      )}
    </motion.div>
  );
}

// layoutId - 다른 컴포넌트 간 공유 레이아웃 애니메이션 (Shared Layout)
// 리스트 → 상세 페이지 전환 같은 효과
function ListItem({ item, onClick }) {
  return (
    <motion.div layoutId={`card-${item.id}`} onClick={onClick}>
      <motion.img layoutId={`image-${item.id}`} src={item.img} />
      <motion.h3 layoutId={`title-${item.id}`}>{item.title}</motion.h3>
    </motion.div>
  );
}

function DetailView({ item }) {
  return (
    <motion.div layoutId={`card-${item.id}`}>
      <motion.img layoutId={`image-${item.id}`} src={item.img} />
      <motion.h3 layoutId={`title-${item.id}`}>{item.title}</motion.h3>
      <p>상세 내용...</p>
    </motion.div>
  );
}

4. GSAP vs Framer Motion — 뭘 써야 할까? 🤔

"그래서 뭘 써야 해?" 라는 질문이 당연히 나오지. 정답은 없고 상황에 따라 달라. 아래 비교표를 보면서 판단해봐!

비교 항목 GSAP Framer Motion
프레임워크 프레임워크 무관 (바닐라 JS, React, Vue, Angular 모두 OK) React 전용
코딩 스타일 명령형 (어떻게 할지 직접 지시) 선언형 (무엇을 할지 선언)
성능 업계 최고 수준 (특히 복잡한 애니메이션) 좋음 (일반적인 UI에 충분)
번들 크기 코어 ~27KB (gzip) ~50KB (gzip)
학습 곡선 중간 (API가 많지만 직관적) 낮음 (React 개발자에게 자연스러움)
스크롤 애니메이션 ScrollTrigger 플러그인 (매우 강력) useScroll 훅 (충분히 강력)
SVG 애니메이션 매우 강력 (모핑, 드로잉 등) 기본적인 수준
제스처 별도 구현 필요 내장 지원 (whileHover, whileTap 등)
레이아웃 애니메이션 직접 구현 필요 layout prop으로 자동 처리
가격 코어 무료, 일부 플러그인 유료 완전 무료 (MIT)
💚 GSAP을 선택해야 할 때

• React 외 프레임워크 사용 중
• 복잡한 시퀀스 애니메이션 필요
• 고성능 SVG 애니메이션
• 스크롤 기반 스토리텔링 사이트
• 게임이나 인터랙티브 경험
• 크로스브라우저 완벽 지원 필요
⚛️ Framer Motion을 선택해야 할 때

• React 프로젝트
• UI 컴포넌트 애니메이션
• 마운트/언마운트 전환 효과
• 드래그 앤 드롭 인터페이스
• 레이아웃 변화 애니메이션
• 빠른 개발 속도 중요
💡 둘 다 쓰면 안 되나요? 당연히 같이 써도 돼! 실제로 React 프로젝트에서 기본 UI 애니메이션은 Framer Motion으로, 복잡한 스크롤 애니메이션이나 SVG 효과는 GSAP으로 처리하는 경우도 많아. 상황에 맞게 조합하는 게 프로의 방식이야! 😎

5. 애니메이션 성능 최적화 — 버벅임 없는 60fps 달성하기 ⚡

아무리 멋진 애니메이션도 버벅이면 의미 없잖아. 성능 최적화는 필수야!

브라우저 렌더링 파이프라인과 성능 최적화 포인트 JavaScript DOM 조작 이벤트 처리 Style CSS 계산 스타일 적용 Layout 크기/위치 계산 ⚠️ 비용 큼 Paint 픽셀 그리기 ⚠️ 비용 큼 Composite 레이어 합성 ✅ 가장 빠름 transform, opacity만 해당 → → → → ❌ 피해야 할 속성 (Layout 유발) width, height, top, left, margin, padding font-size, border, display 등 → 변경 시 전체 레이아웃 재계산 발생! ✅ 권장 속성 (Composite만) transform: translate, scale, rotate opacity → GPU 가속, 60fps 유지 가능! 애니메이션은 transform과 opacity만 사용하는 것이 성능의 핵심!

5-1. will-change와 GPU 가속 🎮

/* CSS에서 GPU 가속 힌트 주기 */
.animated-element {
  will-change: transform, opacity; /* 브라우저에게 미리 알림 */
  /* 또는 */
  transform: translateZ(0); /* 레이어 강제 생성 (구식 방법) */
}

/* 주의: will-change 남발 금지! 메모리 사용량 증가 */
/* 애니메이션 시작 전에 추가하고, 끝나면 제거하는 게 좋음 */

// JavaScript에서 동적으로 처리
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});
element.addEventListener('animationend', () => {
  element.style.willChange = 'auto'; // 끝나면 제거
});

5-2. 성능 최적화 체크리스트 ✅

1
transform과 opacity만 애니메이션 — width, height, top, left 같은 레이아웃 속성 변경은 리플로우를 유발해서 성능이 떨어져.
2
requestAnimationFrame 사용 — setInterval/setTimeout 대신 rAF를 써서 브라우저 렌더링 사이클에 맞춰.
3
레이아웃 스래싱 방지 — 읽기(getBoundingClientRect 등)와 쓰기(style 변경)를 분리해서 배치해.
4
불필요한 애니메이션 제거 — prefers-reduced-motion 미디어 쿼리를 체크해서 접근성도 챙겨!
5
애니메이션 요소 수 최소화 — 동시에 너무 많은 요소를 애니메이션하면 성능이 떨어져. 필요한 것만!
6
DevTools로 프로파일링 — Chrome DevTools의 Performance 탭에서 실제 프레임 드롭을 확인하고 최적화해.
// prefers-reduced-motion 체크 (접근성!)
const prefersReducedMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (!prefersReducedMotion) {
  // 애니메이션 실행
  gsap.from(".hero", { y: 50, opacity: 0, duration: 1 });
} else {
  // 애니메이션 없이 바로 표시
  gsap.set(".hero", { opacity: 1 });
}

// Framer Motion에서는 더 간단하게
import { useReducedMotion } from "framer-motion";

function AnimatedComponent() {
  const shouldReduceMotion = useReducedMotion();

  return (
    <motion.div
      animate={{ opacity: 1, y: 0 }}
      initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
    >
      내용
    </motion.div>
  );
}

6. 실전 예제 모음 — 바로 써먹을 수 있는 코드들 🛠️

6-1. GSAP으로 페이지 로딩 인트로 애니메이션 🎬

// 페이지 로딩 시 순차적으로 등장하는 인트로 애니메이션
function initIntroAnimation() {
  const tl = gsap.timeline({ defaults: { ease: "power3.out" } });

  tl.from(".logo", {
    y: -30,
    opacity: 0,
    duration: 0.8,
  })
  .from(".nav-item", {
    y: -20,
    opacity: 0,
    duration: 0.5,
    stagger: 0.1,
  }, "-=0.4")
  .from(".hero-title", {
    y: 60,
    opacity: 0,
    duration: 1,
    ease: "expo.out",
  }, "-=0.2")
  .from(".hero-subtitle", {
    y: 30,
    opacity: 0,
    duration: 0.8,
  }, "-=0.6")
  .from(".hero-cta", {
    scale: 0.8,
    opacity: 0,
    duration: 0.6,
    ease: "back.out(1.7)",
  }, "-=0.4");
}

document.addEventListener("DOMContentLoaded", initIntroAnimation);

6-2. GSAP ScrollTrigger로 섹션 등장 효과 📜

// 스크롤 시 각 섹션 등장 효과
gsap.utils.toArray(".section").forEach((section) => {
  const elements = section.querySelectorAll(".animate-in");

  gsap.from(elements, {
    y: 60,
    opacity: 0,
    duration: 0.8,
    stagger: 0.15,
    ease: "power2.out",
    scrollTrigger: {
      trigger: section,
      start: "top 75%",
      toggleActions: "play none none none",
    }
  });
});

// 수평 스크롤 섹션 (가로로 스크롤되는 효과)
const panels = gsap.utils.toArray(".panel");
gsap.to(panels, {
  xPercent: -100 * (panels.length - 1),
  ease: "none",
  scrollTrigger: {
    trigger: ".horizontal-section",
    pin: true,
    scrub: 1,
    snap: 1 / (panels.length - 1),
    end: () => "+=" + document.querySelector(".horizontal-section").offsetWidth,
  }
});

6-3. Framer Motion으로 탭 전환 애니메이션 🗂️

import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";

const tabVariants = {
  enter: (direction) => ({
    x: direction > 0 ? 300 : -300,
    opacity: 0,
  }),
  center: {
    x: 0,
    opacity: 1,
  },
  exit: (direction) => ({
    x: direction < 0 ? 300 : -300,
    opacity: 0,
  }),
};

function TabPanel({ tabs }) {
  const [activeTab, setActiveTab] = useState(0);
  const [direction, setDirection] = useState(0);

  const handleTabChange = (newTab) => {
    setDirection(newTab > activeTab ? 1 : -1);
    setActiveTab(newTab);
  };

  return (
    <div>
      {/* 탭 버튼들 */}
      <div style={{ display: "flex", gap: 8 }}>
        {tabs.map((tab, i) => (
          <motion.button
            key={i}
            onClick={() => handleTabChange(i)}
            whileHover={{ scale: 1.05 }}
            whileTap={{ scale: 0.95 }}
            style={{
              background: activeTab === i ? "#7bb8f0" : "#eee",
              color: activeTab === i ? "#fff" : "#333",
            }}
          >
            {tab.label}
            {activeTab === i && (
              <motion.div
                layoutId="activeIndicator"
                style={{
                  position: "absolute",
                  bottom: 0,
                  height: 2,
                  background: "#fff",
                  width: "100%",
                }}
              />
            )}
          </motion.button>
        ))}
      </div>

      {/* 탭 콘텐츠 */}
      <div style={{ overflow: "hidden" }}>
        <AnimatePresence custom={direction} mode="wait">
          <motion.div
            key={activeTab}
            custom={direction}
            variants={tabVariants}
            initial="enter"
            animate="center"
            exit="exit"
            transition={{ duration: 0.3, ease: "easeInOut" }}
          >
            {tabs[activeTab].content}
          </motion.div>
        </AnimatePresence>
      </div>
    </div>
  );
}

6-4. Framer Motion으로 드래그 가능한 카드 🃏

import { motion, useMotionValue, useTransform } from "framer-motion";

function SwipeCard({ card, onSwipe }) {
  const x = useMotionValue(0);
  const rotate = useTransform(x, [-200, 200], [-30, 30]);
  const opacity = useTransform(x, [-200, -100, 0, 100, 200], [0, 1, 1, 1, 0]);

  // 좋아요/싫어요 표시 투명도
  const likeOpacity = useTransform(x, [0, 100], [0, 1]);
  const nopeOpacity = useTransform(x, [-100, 0], [1, 0]);

  const handleDragEnd = (event, info) => {
    if (info.offset.x > 100) {
      onSwipe("right"); // 오른쪽으로 스와이프 = 좋아요
    } else if (info.offset.x < -100) {
      onSwipe("left"); // 왼쪽으로 스와이프 = 싫어요
    }
  };

  return (
    <motion.div
      style={{ x, rotate, opacity }}
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      dragElastic={0.7}
      onDragEnd={handleDragEnd}
      whileDrag={{ cursor: "grabbing" }}
    >
      {/* 좋아요 표시 */}
      <motion.div style={{ opacity: likeOpacity }}>
        👍 LIKE
      </motion.div>

      {/* 싫어요 표시 */}
      <motion.div style={{ opacity: nopeOpacity }}>
        👎 NOPE
      </motion.div>

      <img src={card.image} alt={card.title} />
      <h3>{card.title}</h3>
    </motion.div>
  );
}

7. 알아두면 좋은 추가 도구들과 생태계 🌐

7-1. 다른 유용한 애니메이션 라이브러리들 📚

라이브러리 특징 추천 용도
Anime.js 가볍고 직관적인 API, SVG 지원 간단한 프로젝트, 빠른 프로토타입
Three.js 3D 그래픽 및 애니메이션 3D 인터랙티브 경험
Lottie After Effects 애니메이션을 웹에서 디자이너 협업, 복잡한 일러스트 애니메이션
Motion One Web Animations API 기반, 초경량 번들 크기 중요한 프로젝트
React Spring 물리 기반 스프링 애니메이션 자연스러운 물리 효과 필요 시
Popmotion Framer Motion의 기반 라이브러리 저수준 애니메이션 제어

7-2. 유용한 리소스와 학습 자료 📖

🔗 꼭 북마크해야 할 사이트들 • GSAP 공식 문서: gsap.com/docs — 예제가 정말 풍부해
• GSAP Ease Visualizer: gsap.com/docs/v3/Eases — 이징 함수 시각화
• Framer Motion 공식 문서: framer.com/motion — 인터랙티브 예제 포함
• CSS Tricks: css-tricks.com — 다양한 애니메이션 기법
• CodePen: codepen.io — 실제 구현 예제 무궁무진
• 재능넷: jaenung.net — 애니메이션 전문 개발자를 찾거나 자신의 실력을 공유할 수 있어! 🎨

7-3. 2024년 트렌드 — View Transitions API 👀

최근에 주목받고 있는 건 브라우저 네이티브 View Transitions API야. 페이지 전환 시 부드러운 애니메이션을 라이브러리 없이 구현할 수 있어!

// View Transitions API (Chrome 111+, Safari 18+)
async function navigateWithTransition(url) {
  if (!document.startViewTransition) {
    // 지원 안 하는 브라우저는 그냥 이동
    window.location.href = url;
    return;
  }

  const transition = document.startViewTransition(async () => {
    // DOM 업데이트 (페이지 내용 변경)
    await updatePageContent(url);
  });

  await transition.finished;
}

// CSS로 전환 효과 커스터마이징
/* CSS */
/*
::view-transition-old(root) {
  animation: fade-out 0.3s ease;
}
::view-transition-new(root) {
  animation: fade-in 0.3s ease;
}

@keyframes fade-out {
  to { opacity: 0; transform: translateY(-20px); }
}
@keyframes fade-in {
  from { opacity: 0; transform: translateY(20px); }
}
*/
💡 View Transitions API는 아직 모든 브라우저에서 지원하지 않아서 프로덕션에서는 폴리필이나 점진적 향상(Progressive Enhancement) 방식으로 사용해야 해. 하지만 미래가 기대되는 기술이야!

8. 마무리 — 애니메이션, 이제 두렵지 않지? 🎊

JavaScript 애니메이션 학습 로드맵 🗺️ 1️⃣ 기초 다지기 CSS 트랜지션 CSS 애니메이션 rAF 이해 → 2️⃣ WAAPI 학습 Web Animations API 마스터 이징 함수 이해 → 3️⃣ GSAP 정복 트윈 & 타임라인 ScrollTrigger 플러그인 활용 → 4️⃣ Framer Motion React 통합 variants & 제스처 레이아웃 애니메이션 → 5️⃣ 성능 최적화 GPU 가속 접근성 고려 프로파일링 🎯 핵심 원칙 애니메이션은 UX를 향상시키는 도구! 과하면 오히려 방해가 돼. 목적 있는 애니메이션 + 성능 최적화 + 접근성 = 완벽한 웹 경험 ✨

자, 여기까지 JavaScript 애니메이션의 세계를 함께 탐험해봤어! 😊

정리하자면:

✓
바닐라 JS는 requestAnimationFrame과 Web Animations API로 기본기를 다질 수 있어.
✓
GSAP은 프레임워크 무관하게 쓸 수 있고, 복잡한 시퀀스 애니메이션과 스크롤 연동에 최강이야.
✓
Framer Motion은 React 개발자라면 필수! 선언형 API로 직관적이고 빠르게 구현할 수 있어.
✓
성능 최적화는 transform과 opacity만 사용하고, 접근성(prefers-reduced-motion)도 꼭 챙겨야 해.


애니메이션은 단순히 "예쁘게 만들기"가 아니야. 사용자에게 맥락을 전달하고, 피드백을 주고, 경험을 풍부하게 만드는 중요한 UX 도구야. 목적 없는 애니메이션은 오히려 사용자를 피곤하게 만들 수 있으니, 항상 "이 애니메이션이 왜 필요한가?"를 먼저 생각해봐!

만약 애니메이션 개발 실력을 더 키우고 싶거나, 전문 개발자의 도움이 필요하다면 재능넷(jaenung.net)에서 다양한 프론트엔드 전문가들을 만나볼 수 있어. 실력 있는 개발자들이 많이 활동하고 있으니 참고해봐! 🌟

이제 코드 에디터 열고 직접 만들어봐. 처음엔 어색해도 계속 만들다 보면 어느 순간 "오, 이거 내가 만든 거야?" 하는 순간이 올 거야. 그 순간이 진짜 재미있거든! 💪

🎁 마지막 꿀팁! GSAP과 Framer Motion 모두 공식 문서에 인터랙티브 데모가 엄청 잘 되어 있어. 코드를 직접 수정하면서 결과를 바로 볼 수 있거든. 이론 공부보다 이 데모들을 직접 만져보는 게 훨씬 빠르게 익힐 수 있는 방법이야! 🚀
댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.