--- 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]]