[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user