Skip to Content
FoundationsFoundationAnimations (애니메이션)

Animations (애니메이션)

Foundation의 애니메이션 시스템은 Transition DurationEasing Functions을 제공하여 부드럽고 일관된 UI 상호작용을 표현합니다.

애니메이션 철학

Foundation의 애니메이션은 다음 원칙을 따릅니다:

  • Purposeful: 사용자 경험을 향상시키는 의미 있는 애니메이션
  • Subtle: 과하지 않고 자연스러운 움직임
  • Performance: 60fps를 목표로 하는 최적화
  • Accessibility: prefers-reduced-motion 미디어 쿼리 지원

Transition Duration (전환 시간)

Foundation은 3단계 Transition Duration을 제공합니다.

실제 토큰 값 (core.css)

@theme { /* Transitions */ --transition-fast: 150ms; --transition-base: 200ms; --transition-slow: 300ms; }

사용 가이드

토큰시간사용 사례
fast150msHover, Focus, Small Changes
base200msModal Open/Close, Dropdown (기본)
slow300msSlide Animation, Large Changes

Easing Functions (이징 함수)

Tailwind 기본 Easing

EasingCSS Value사용 사례
linearlinearProgress Bar, Loading
ease-incubic-bezier(0.4, 0, 1, 1)Fade Out
ease-outcubic-bezier(0, 0, 0.2, 1)Fade In (권장)
ease-in-outcubic-bezier(0.4, 0, 0.2, 1)Modal, Dialog

사용 방법

CSS Variables로 사용

.button { transition: background-color var(--transition-fast) ease-out; } .modal { transition: opacity var(--transition-base) ease-in-out; }

Tailwind Utility Classes로 사용

<button className="transition-colors duration-150 ease-out hover:bg-primary"> Hover me </button>

컴포넌트별 애니메이션 가이드

Button Hover

<button className="px-md py-sm bg-primary text-white rounded transition-colors duration-150 hover:bg-primary-600"> Hover Button </button>

Card Elevation

<div className="p-md bg-white rounded-lg shadow-md transition-shadow duration-200 hover:shadow-xl"> Hover for elevation </div>
export default function Modal({ isOpen }) { return ( <div className={` fixed inset-0 bg-black transition-opacity duration-200 ${isOpen ? "opacity-50" : "opacity-0 pointer-events-none"} `} > <div className={` fixed inset-0 flex items-center justify-center p-md transition-all duration-300 ease-out ${isOpen ? "opacity-100 scale-100" : "opacity-0 scale-95"} `} > <div className="bg-white rounded-lg shadow-xl p-lg max-w-md"> Modal Content </div> </div> </div> ); }
export default function Dropdown({ isOpen }) { return ( <div className="relative"> <button className="px-md py-sm bg-white border rounded">Open Menu</button> <div className={` absolute top-full left-0 mt-xs bg-white border rounded-lg shadow-lg transition-all duration-200 ease-out origin-top ${ isOpen ? "opacity-100 scale-100" : "opacity-0 scale-95 pointer-events-none" } `} > <a href="#" className="block px-md py-sm hover:bg-gray-50"> Item 1 </a> <a href="#" className="block px-md py-sm hover:bg-gray-50"> Item 2 </a> </div> </div> ); }

Transform Animations

Scale

<button className="transform transition-transform duration-150 hover:scale-105 active:scale-95"> Scale Button </button>

Translate

<button className="transform transition-transform duration-200 hover:translate-y-[-2px]"> Lift Button </button>

Rotate

<button className="group"> <ChevronDown className="transform transition-transform duration-200 group-hover:rotate-180" /> </button>

Loading Animations

Spinner

export default function Spinner() { return ( <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div> ); }

Pulse

<div className="animate-pulse bg-gray-200 h-4 w-32 rounded"></div>

Bounce

<div className="animate-bounce">↓</div>

실전 예제

Toast Notification

export default function Toast({ isVisible }) { return ( <div className={` fixed bottom-md right-md bg-white rounded-lg shadow-xl p-md transition-all duration-300 ease-out ${ isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4 pointer-events-none" } `} > <p className="text-gray-900">Notification message</p> </div> ); }

Tab Transition

export default function Tabs() { const [activeTab, setActiveTab] = useState(0); return ( <div> <div className="flex gap-sm border-b"> {["Tab 1", "Tab 2", "Tab 3"].map((tab, index) => ( <button key={index} onClick={() => setActiveTab(index)} className={` px-md py-sm border-b-2 transition-colors duration-150 ${ activeTab === index ? "border-primary text-primary" : "border-transparent text-gray-600 hover:text-gray-900" } `} > {tab} </button> ))} </div> <div className="mt-md"> <div className="transition-opacity duration-200"> Tab {activeTab + 1} Content </div> </div> </div> ); }

Skeleton Loading

export default function SkeletonCard() { return ( <div className="p-md bg-white border rounded-lg"> <div className="animate-pulse space-y-md"> <div className="h-4 bg-gray-200 rounded w-3/4"></div> <div className="h-4 bg-gray-200 rounded"></div> <div className="h-4 bg-gray-200 rounded w-5/6"></div> </div> </div> ); }

접근성 고려사항

prefers-reduced-motion

애니메이션에 민감한 사용자를 위해 prefers-reduced-motion 미디어 쿼리를 지원하세요.

@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }

Tailwind로 적용

<button className="transition-transform motion-reduce:transition-none hover:scale-105"> Accessible Animation </button>

성능 최적화

GPU 가속 속성 사용

다음 속성은 GPU 가속을 사용하여 60fps 달성:

  • transform (translate, scale, rotate)
  • opacity
  • filter

피해야 할 속성

다음 속성은 Reflow를 발생시켜 성능 저하:

  • width, height
  • top, left, right, bottom
  • margin, padding

권장 패턴

// ✅ Good: transform 사용 <div className="transition-transform hover:translate-x-2"> // ❌ Bad: left 사용 (Reflow 발생) <div className="transition-all hover:left-2">

다음 단계

Last updated on