---
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';
Content
// Variants
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.1 } },
};
{items.map(i => (
{i.text}
))}
```
### Layout animation (μλ)
```tsx
{/* size / position λ³κ²½ μλ animate */}
{items.map(i => (
{i.text}
))}
```
### Gesture
```tsx
Drag me
```
### Spring physics
```tsx
```
### 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
...
;
}
```
### 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
```
β νμ΄μ§ / 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
...
```
β 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 (
);
}
```
```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';
```
β After Effects μμ export. νλΆ μ λλ©μ΄μ
.
### ν° list animation
```tsx
// AnimatePresence + ν° list = λλ¦Ό
// μκ² λλ virtualize + minimal animation
```
### Staggered (timeline-like)
```tsx
{items.map(i => (
{i.text}
))}
```
## π€ μμ¬κ²°μ κΈ°μ€
| μν© | μΆμ² |
|---|---|
| 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]]