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

JavaScript 애니메이션의 모든 것 — GSAP & Framer Motion 완전 정복 가이드 🚀
바닐라 JS부터 최강 라이브러리까지, 웹 애니메이션의 세계를 같이 탐험해보자!
안녕! 오늘은 웹 개발자라면 한 번쯤은 꼭 마주치게 되는 그 주제, 바로 JavaScript 애니메이션에 대해 진짜 깊이 파고들어볼 거야. 😄
"애니메이션? 그냥 CSS로 하면 되는 거 아니야?" 라고 생각할 수도 있는데, 실제로 복잡한 인터랙션이나 시퀀스 애니메이션, 물리 기반 움직임 같은 걸 구현하다 보면 CSS만으로는 한계가 느껴지거든. 그때 등장하는 게 바로 GSAP과 Framer Motion 같은 강력한 라이브러리들이야!
이 글에서는 바닐라 JavaScript 애니메이션의 기초부터 시작해서, 업계에서 가장 많이 쓰이는 두 라이브러리의 핵심 개념과 실전 활용법까지 싹 다 정리해줄게. 커피 한 잔 들고 편하게 읽어봐! ☕
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); // 시작!
• 모니터 주사율에 맞춰 자동 최적화 (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('애니메이션 완료! 🎉');
2. GSAP — 프로 개발자들의 애니메이션 무기 💚
GSAP(GreenSock Animation Platform)은 2008년부터 시작된 역사 깊은 JavaScript 애니메이션 라이브러리야. 지금도 업계 표준으로 불릴 만큼 강력하고, 넷플릭스, 구글, 애플 같은 대기업들도 사용하는 검증된 도구야. 🏆
2023년 기준으로 GSAP 3.x 버전이 최신이고, 핵심 기능은 무료로 사용할 수 있어. (일부 고급 플러그인은 클럽 멤버십 필요)
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("진행 중..."),
});
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", // 사이드바를 화면에 고정
}
});
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단계로 끊어서 이동 | 스프라이트 애니메이션 |
3. Framer Motion — React 개발자의 최애 라이브러리 ⚛️
Framer Motion은 React 전용 애니메이션 라이브러리야. 2019년에 Framer 팀에서 만들었고, 현재 버전 11.x까지 나왔어. React 생태계에서 가장 인기 있는 애니메이션 라이브러리 중 하나야.
GSAP이 "명령형(Imperative)" 방식이라면, Framer Motion은 선언형(Declarative) 방식이야. 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 객체야. 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) |
• React 외 프레임워크 사용 중
• 복잡한 시퀀스 애니메이션 필요
• 고성능 SVG 애니메이션
• 스크롤 기반 스토리텔링 사이트
• 게임이나 인터랙티브 경험
• 크로스브라우저 완벽 지원 필요
• React 프로젝트
• UI 컴포넌트 애니메이션
• 마운트/언마운트 전환 효과
• 드래그 앤 드롭 인터페이스
• 레이아웃 변화 애니메이션
• 빠른 개발 속도 중요
5. 애니메이션 성능 최적화 — 버벅임 없는 60fps 달성하기 ⚡
아무리 멋진 애니메이션도 버벅이면 의미 없잖아. 성능 최적화는 필수야!
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. 성능 최적화 체크리스트 ✅
prefers-reduced-motion 미디어 쿼리를 체크해서 접근성도 챙겨!// 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 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); }
}
*/
8. 마무리 — 애니메이션, 이제 두렵지 않지? 🎊
자, 여기까지 JavaScript 애니메이션의 세계를 함께 탐험해봤어! 😊
정리하자면:
requestAnimationFrame과 Web Animations API로 기본기를 다질 수 있어.
애니메이션은 단순히 "예쁘게 만들기"가 아니야. 사용자에게 맥락을 전달하고, 피드백을 주고, 경험을 풍부하게 만드는 중요한 UX 도구야. 목적 없는 애니메이션은 오히려 사용자를 피곤하게 만들 수 있으니, 항상 "이 애니메이션이 왜 필요한가?"를 먼저 생각해봐!
만약 애니메이션 개발 실력을 더 키우고 싶거나, 전문 개발자의 도움이 필요하다면 재능넷(jaenung.net)에서 다양한 프론트엔드 전문가들을 만나볼 수 있어. 실력 있는 개발자들이 많이 활동하고 있으니 참고해봐! 🌟
이제 코드 에디터 열고 직접 만들어봐. 처음엔 어색해도 계속 만들다 보면 어느 순간 "오, 이거 내가 만든 거야?" 하는 순간이 올 거야. 그 순간이 진짜 재미있거든! 💪
관련 키워드
댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.
아직 댓글이 없습니다.