---
id: frontend-animation-motion
title: Animation — Motion / GSAP / View Transitions
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [frontend, animation, motion, gsap, vibe-coding]
tech_stack: { language: "TS / React", applicable_to: ["Frontend"] }
applied_in: []
aliases: [Framer Motion, Motion, GSAP, View Transitions API, CSS animation, Web Animations API]
---

# Frontend Animation

> Modern stack: **Motion (Framer Motion 후속) / GSAP / View Transitions API**. CSS / Web Animations API / RequestAnimationFrame. 60fps + a11y `prefers-reduced-motion`.

## 📖 핵심 개념
- Declarative (Motion / Framer): React 친화.
- Imperative (GSAP / WAAPI): 강력 / 정밀.
- View Transitions: 페이지 전환 자동 (browser native).
- Hardware-accelerated: transform / opacity 만.

## 💻 코드 패턴

### Motion (Framer Motion 후속)
```tsx
import { motion } from 'motion/react';

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0 }}
  transition={{ duration: 0.3, ease: 'easeOut' }}
>
  Content
</motion.div>

// Variants
const variants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1, transition: { staggerChildren: 0.1 } },
};

<motion.ul variants={variants} initial="hidden" animate="visible">
  {items.map(i => (
    <motion.li key={i.id} variants={{ hidden: { opacity: 0, x: -20 }, visible: { opacity: 1, x: 0 } }}>
      {i.text}
    </motion.li>
  ))}
</motion.ul>
```

### Layout animation (자동)
```tsx
<motion.div layout>
  {/* size / position 변경 자동 animate */}
</motion.div>

<AnimatePresence mode="popLayout">
  {items.map(i => (
    <motion.div key={i.id} layout exit={{ opacity: 0 }}>
      {i.text}
    </motion.div>
  ))}
</AnimatePresence>
```

### Gesture
```tsx
<motion.div
  drag dragConstraints={{ left: 0, right: 200 }}
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  whileDrag={{ scale: 1.1 }}
>
  Drag me
</motion.div>
```

### Spring physics
```tsx
<motion.div
  animate={{ x: 100 }}
  transition={{ type: 'spring', stiffness: 200, damping: 20 }}
/>
```

### GSAP (강력 / 복잡)
```ts
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

// Timeline
const tl = gsap.timeline();
tl.to('.box', { x: 100, duration: 1 })
  .to('.box', { y: 100, duration: 0.5 })
  .to('.box', { opacity: 0, duration: 0.3 });

// Scroll trigger
gsap.to('.parallax', {
  y: -200,
  scrollTrigger: {
    trigger: '.section',
    start: 'top top',
    end: 'bottom top',
    scrub: 1,
  },
});

// React useGSAP
import { useGSAP } from '@gsap/react';

function Component() {
  const ref = useRef(null);
  useGSAP(() => {
    gsap.from('.item', { y: 50, opacity: 0, stagger: 0.1 });
  }, { scope: ref });
  
  return <div ref={ref}>...</div>;
}
```

### View Transitions API (browser native)
```css
@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

::view-transition-group(card) {
  animation: cardMorph 0.5s;
}
```

```html
<img style="view-transition-name: hero" src="...">
```

→ 페이지 / view 변경이 부드럽게.

```ts
// SPA 사용
async function navigate(url: string) {
  if (!document.startViewTransition) return goto(url);
  
  document.startViewTransition(async () => {
    await goto(url);
  });
}
```

→ Chrome / Edge / Safari 17.4+. Polyfill X.

### CSS animations (가벼운)
```css
.fade-in {
  animation: fadeIn 0.3s ease-out forwards;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .fade-in { animation: none; }
}
```

```tsx
<div className="fade-in">...</div>
```

→ Build 작은 + browser 빠름.

### Web Animations API (modern, vanilla)
```ts
const el = document.querySelector('.box')!;
const anim = el.animate(
  [
    { opacity: 0, transform: 'translateY(20px)' },
    { opacity: 1, transform: 'translateY(0)' },
  ],
  { duration: 300, easing: 'ease-out', fill: 'forwards' }
);

await anim.finished;
```

### a11y — prefers-reduced-motion
```tsx
import { useReducedMotion } from 'motion/react';

function Component() {
  const reduced = useReducedMotion();
  return (
    <motion.div
      animate={{ x: reduced ? 0 : 100 }}
    />
  );
}
```

```ts
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
```

→ 사용자가 setting 으로 끄면 응답.

### Performance — transform / opacity 만
```css
/* ✅ 60fps (GPU) */
.move { transform: translateX(100px); }
.fade { opacity: 0.5; }

/* ❌ Reflow (CPU) */
.bad { left: 100px; }
.bad { width: 200px; }
.bad { margin-top: 50px; }
```

→ `will-change: transform` (cautious — 메모리).

### FLIP technique (자체 구현 layout animation)
```ts
// First — 시작 위치
const start = el.getBoundingClientRect();
// Last — 변경 후 위치
performLayoutChange();
const end = el.getBoundingClientRect();
// Invert — 차이 적용
const dx = start.left - end.left;
el.style.transform = `translateX(${dx}px)`;
// Play — 0 으로
requestAnimationFrame(() => {
  el.style.transition = 'transform 0.3s';
  el.style.transform = '';
});
```

→ Motion 의 `layout` 이 FLIP 자동.

### Lottie (designer 친화)
```bash
npm install lottie-react
```

```tsx
import Lottie from 'lottie-react';
import animationData from './animation.json';

<Lottie animationData={animationData} loop play />
```

→ After Effects 에서 export. 풍부 애니메이션.

### 큰 list animation
```tsx
// AnimatePresence + 큰 list = 느림
// 작게 또는 virtualize + minimal animation
```

### Staggered (timeline-like)
```tsx
<motion.div
  variants={{
    visible: { transition: { staggerChildren: 0.05, delayChildren: 0.2 } },
  }}
  initial="hidden"
  animate="visible"
>
  {items.map(i => (
    <motion.div key={i.id} variants={itemVariants}>
      {i.text}
    </motion.div>
  ))}
</motion.div>
```

## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| React 일반 | Motion (Framer 후속) |
| 매우 복잡 / 강력 | GSAP |
| 간단 (전환 / fade) | CSS animation |
| Vanilla / 가벼움 | WAAPI |
| 페이지 전환 | View Transitions API |
| Designer animation | Lottie |
| Skia / canvas | Reanimated (RN) / Pixi (web) |

## ❌ 안티패턴
- **width / left animation**: reflow — jank.
- **prefers-reduced-motion 무시**: a11y / 멀미.
- **너무 길음 (1초+ 일반)**: 사용자 지루.
- **모든 component animate**: noise.
- **Lottie 거대 JSON (500KB+)**: bundle 큼.
- **GSAP transition 매번 cleanup 안 함**: leak.
- **CSS animation + JS animation 같은 element**: 충돌.

## 🤖 LLM 활용 힌트
- Motion = React 표준 (variants, layout, AnimatePresence).
- GSAP = 강력 timeline / scroll.
- View Transitions = page transition.
- prefers-reduced-motion 항상.

## 🔗 관련 문서
- [[React_Animation_Performance]]
- [[Frontend_A11y_Testing]]
- [[Web_Performance_Core_Vitals]]
