왜 애니메이션이 중요한가?
버튼을 클릭했을 때 아무 반응이 없으면 "눌린 건가?" 싶습니다. 호버했을 때 색이 툭 바뀌면 어색하죠. 부드러운 애니메이션은 사용자에게 "잘 동작하고 있어요"라는 피드백을 줍니다.
좋은 UI 애니메이션의 원칙:
- 목적이 있어야 합니다 — 예쁘기만 하면 안 되고, 사용자 경험을 개선해야 합니다
- 빨라야 합니다 — 대부분의 UI 트랜지션은 200~300ms가 적당합니다
- 자연스러워야 합니다 — ease-out, ease-in-out 같은 타이밍 함수를 사용합니다
transition: 상태 변화를 부드럽게
CSS 속성이 변할 때 중간 과정을 자동으로 보간합니다.
.btn {
background: #1a1a1a;
color: white;
transition: background 0.2s ease;
}
.btn:hover {
background: #333;
}transition 없이는 색이 확 바뀌지만, transition을 넣으면 0.2초에 걸쳐 부드럽게 변합니다.
transition 속성 분해
transition: property duration timing-function delay;
/* 예시 */
transition: all 0.3s ease-out;
transition: background 0.2s ease, transform 0.2s ease;
transition: opacity 0.15s ease-in-out 0.1s; /* 0.1초 후 시작 */타이밍 함수 (easing)
| 함수 | 특징 | 용도 |
|---|---|---|
ease | 시작·끝 느리게 | 범용 (기본값) |
ease-in | 시작만 느리게 | 화면 밖으로 나갈 때 |
ease-out | 끝만 느리게 | 화면 안으로 들어올 때 |
ease-in-out | 시작·끝 느리게 | 부드러운 왕복 |
linear | 일정 속도 | 프로그레스 바 |
프로 팁: UI 요소가 나타날 때는 ease-out, 사라질 때는 ease-in을 사용하면 가장 자연스럽습니다.
transform: 변형
요소를 이동, 회전, 확대/축소합니다. 레이아웃을 건드리지 않고(리플로우 없이) GPU로 처리되므로 성능이 뛰어납니다.
/* 이동 */
transform: translateX(20px);
transform: translateY(-10px);
transform: translate(20px, -10px);
/* 크기 */
transform: scale(1.05); /* 5% 확대 */
transform: scaleX(1.5); /* 가로만 1.5배 */
/* 회전 */
transform: rotate(45deg);
transform: rotate(-90deg);
/* 기울이기 */
transform: skewX(10deg);
/* 조합 */
transform: translateY(-4px) scale(1.02);호버 효과 예시
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}카드가 살짝 떠오르면서 그림자가 깊어지는 효과. 많은 웹사이트에서 볼 수 있는 클래식한 패턴입니다.
@keyframes: 복잡한 애니메이션
transition은 A→B 두 상태만 처리합니다. 여러 단계가 필요하면 @keyframes를 사용합니다.
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.element {
animation: fadeIn 0.5s ease-out;
}여러 단계
@keyframes bounce {
0% { transform: translateY(0); }
40% { transform: translateY(-20px); }
60% { transform: translateY(-10px); }
80% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.icon {
animation: bounce 0.6s ease;
}animation 속성
animation: name duration timing delay iteration direction fill;
/* 무한 반복 */
animation: spin 1s linear infinite;
/* 2번만 반복 */
animation: shake 0.3s ease 2;
/* 왕복 */
animation: pulse 2s ease-in-out infinite alternate;| 속성 | 설명 |
|---|---|
infinite | 무한 반복 |
alternate | 왕복 (0→100→0→100...) |
forwards | 끝나면 마지막 상태 유지 |
backwards | 시작 전에 첫 번째 상태 적용 |
실전 레시피
로딩 스피너
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e2e8f0;
border-top-color: #2dd4bf;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}펄스 효과
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton {
animation: pulse 1.5s ease-in-out infinite;
background: #e2e8f0;
border-radius: 4px;
}페이지 진입 애니메이션
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card:nth-child(1) { animation: slideUp 0.4s ease-out 0.0s both; }
.card:nth-child(2) { animation: slideUp 0.4s ease-out 0.1s both; }
.card:nth-child(3) { animation: slideUp 0.4s ease-out 0.2s both; }nth-child로 순차적 딜레이를 주면 카드가 하나씩 올라오는 연출이 됩니다.
성능 최적화
GPU 가속 속성 (빠름)
transform, opacity — 레이아웃을 건드리지 않아서 60fps 유지 가능
CPU 속성 (느림)
width, height, top, left, margin, padding — 리플로우가 발생해서 버벅일 수 있음
규칙: 애니메이션에는 transform과 opacity만 사용하세요.
/* 나쁜 예 */
.box:hover { left: 20px; }
/* 좋은 예 */
.box:hover { transform: translateX(20px); }prefers-reduced-motion
일부 사용자는 애니메이션이 불편할 수 있습니다 (전정기관 장애 등). 접근성을 위해 이 미디어 쿼리를 추가하세요:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}정리
| 기능 | 용도 | 문법 |
|---|---|---|
transition | A→B 부드러운 전환 | transition: all 0.2s ease |
transform | 이동/회전/확대 | transform: translateY(-4px) |
@keyframes | 다단계 애니메이션 | animation: name 0.5s ease |
이 세 가지를 조합하면 전문적인 UI 인터랙션을 만들 수 있습니다.