Animations (애니메이션)
Foundation의 애니메이션 시스템은 Transition Duration과 Easing 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;
}사용 가이드
| 토큰 | 시간 | 사용 사례 |
|---|---|---|
| fast | 150ms | Hover, Focus, Small Changes |
| base | 200ms | Modal Open/Close, Dropdown (기본) |
| slow | 300ms | Slide Animation, Large Changes |
Easing Functions (이징 함수)
Tailwind 기본 Easing
| Easing | CSS Value | 사용 사례 |
|---|---|---|
| linear | linear | Progress Bar, Loading |
| ease-in | cubic-bezier(0.4, 0, 1, 1) | Fade Out |
| ease-out | cubic-bezier(0, 0, 0.2, 1) | Fade In (권장) |
| ease-in-out | cubic-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>Modal Fade In
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>
);
}Dropdown Slide
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)opacityfilter
피해야 할 속성
다음 속성은 Reflow를 발생시켜 성능 저하:
width,heighttop,left,right,bottommargin,padding
권장 패턴
// ✅ Good: transform 사용
<div className="transition-transform hover:translate-x-2">
// ❌ Bad: left 사용 (Reflow 발생)
<div className="transition-all hover:left-2">다음 단계
Last updated on