CSS 애니메이션 가이드: 전환, 키프레임 및 성능
CSS 애니메이션은 인터페이스에 생명을 불어넣어 시각적 피드백을 제공하고, 사용자 주의를 유도하며, 기억에 남는 경험을 만듭니다. 잘 만들어진 애니메이션은 자연스럽고 직관적으로 느껴지지만, 잘못 만들어진 애니메이션은 끊김 현상, 사용자 주의 분산, 심지어 신체적 불편함까지 초래할 수 있습니다. 이 가이드는 전환과 키프레임의 기초부터 고급 성능 최적화 및 접근성까지 모든 것을 다루어, 부드럽고 목적 있으며 포용적인 애니메이션을 만들 수 있는 지식을 제공합니다.
CSS 전환과 애니메이션
CSS는 동작 효과를 만드는 두 가지 메커니즘을 제공합니다: 전환과 애니메이션입니다. 각 메커니즘을 언제 사용할지 이해하는 것이 효과적인 CSS 애니메이션 작업의 기초입니다.
CSS 전환
전환은 CSS 속성의 두 상태 간 변화에 애니메이션을 적용합니다. 애니메이션할 속성, 지속 시간 및 이징 곡선을 정의하면, 브라우저가 시작 값과 끝 값 사이의 보간을 자동으로 처리합니다. 전환은 호버, 포커스, 클래스 추가 또는 JavaScript 속성 변경과 같은 상태 변화에 의해 트리거됩니다.
.button {
background: #3b82f6;
transform: scale(1);
transition: background 0.3s ease, transform 0.2s ease;
}
.button:hover {
background: #2563eb;
transform: scale(1.05);
}전환은 간단한 두 상태 애니메이션에 완벽합니다. 최소한의 코드만 필요하고, 이해하기 쉬우며, 브라우저에서 안정적으로 작동합니다. 제한점은 두 값 사이에서만 애니메이션된다는 점입니다——중간 단계를 정의할 수 없습니다.
CSS 애니메이션 (키프레임)
애니메이션은 @keyframes 규칙을 사용하여 임의 개수의 단계로 스타일 변화 시퀀스를 정의합니다. 전환과 달리, 애니메이션은 상태 변화와 독립적으로 실행되고, 무한 반복되고, 역방향 재생되고, 일시 정지될 수 있습니다. 애니메이션의 모든 프레임을 정밀하게 제어할 수 있습니다.
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.pulse-element {
animation: pulse 2s ease-in-out infinite;
}언제 어떤 것을 사용할까
| 시나리오 | 전환 사용 | 애니메이션 사용 |
|---|---|---|
| 호버 효과 | 예 | 아니요 |
| 포커스 상태 | 예 | 아니요 |
| 가시성 전환 | 예 | 가능 |
| 다단계 시퀀스 | 아니요 | 예 |
| 반복 애니메이션 | 아니요 | 예 |
| 상태 독립적 | 아니요 | 예 |
| 진입 애니메이션 | 가능 | 예 |
키프레임 구문 심층 분석
@keyframes 규칙은 CSS 애니메이션의 핵심입니다. 그 구문을 충분히 이해하면 다단계 애니메이션 시퀀스의 모든 힘을 발휘할 수 있습니다.
백분율 기반 키프레임
키프레임은 0%에서 100%까지의 백분율 값으로 정의되며, 애니메이션 타임라인에서의 진행률을 나타냅니다. 필요한 만큼 많은 키프레임을 정의할 수 있으며, 브라우저가 그 사이를 보간합니다:
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
25% {
transform: translateY(-20px);
}
50% {
transform: translateY(0);
}
75% {
transform: translateY(-10px);
}
}여러 키프레임이 쉼표로 구분된 백분율 값을 통해 동일한 스타일 블록을 공유할 수 있습니다(위 예시의 0%, 100%처럼). 이는 시작 상태로 돌아가는 애니메이션에 유용합니다.
애니메이션 단축 속성
animation 단축 속성은 8개의 하위 속성을 하나의 선언으로 결합합니다. 순서는 name, duration, timing-function, delay, iteration-count, direction, fill-mode, play-state입니다.
.element {
animation: slideIn 0.5s ease-out 0.2s 1 forwards running;
}
/* Equivalent longhand */
.element {
animation-name: slideIn;
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-delay: 0.2s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-play-state: running;
}애니메이션 필 모드
animation-fill-mode 속성은 애니메이션 실행 전후에 어떤 스타일이 적용되는지 제어합니다. 이는 가장 자주 오해되는 애니메이션 속성 중 하나입니다:
- none (기본값): 애니메이션이 끝나면 요소가 애니메이션 전 스타일로 돌아갑니다.
- forwards: 애니메이션이 끝난 후 요소가 마지막 프레임의 스타일을 유지합니다. 진입 애니메이션에서 요소가 최종 위치에 머물기를 원할 때 필수적입니다.
- backwards: 애니메이션 지연 기간 동안(애니메이션 시작 전), 요소가 첫 번째 프레임의 스타일을 적용합니다.
- both: forwards와 backwards 동작을 결합합니다. 요소가 지연 기간 동안 첫 번째 프레임을 표시하고, 완료 후 마지막 프레임을 유지합니다.
이징 함수
이징 함수(타이밍 함수라고도 함)는 애니메이션의 가속 곡선을 정의합니다. 애니메이션이 천천히 시작했다가 가속할지, 빠르게 시작했다가 감속할지, 아니면 더 복잡한 패턴을 따를지 결정합니다. 올바른 이징 함수는 애니메이션을 자연스럽게 느끼게 하고, 잘못된 이징 함수는 애니메이션을 기계적이거나 부자연스럽게 만듭니다.
내장 이징 함수
- linear: 전체 애니메이션 동안 속도가 일정합니다. 현실 세계의 물체는 등속으로 움직이지 않기 때문에 자연스러워 보이는 경우가 드뭅니다. 기계적 또는 회전 애니메이션에 적합합니다.
- ease (기본값): 천천히 시작하여 중간에 가속하고 끝에서 감속합니다. 대부분의 UI 전환에 적합한 좋은 범용 이징입니다.
- ease-in: 천천히 시작한 다음 가속합니다.
- ease-out: 빠르게 시작한 다음 감속합니다.
- ease-in-out: 천천히 시작하고 끝나며, 중간에 더 빠릅니다. ease와 유사하지만 가속과 감속이 더 두드러집니다. 더 크고 극적인 전환에 적합합니다.
3차 베지어 곡선
cubic-bezier() 함수를 사용하면 정밀한 제어를 통해 사용자 정의 이징 곡선을 정의할 수 있습니다. 3차 베지어 곡선의 제어점을 나타내는 4개의 매개변수를 받습니다: cubic-bezier(x1, y1, x2, y2). x 값은 시간(0~1)을, y 값은 진행률(0-1을 넘어 오버슈트 효과 구현 가능)을 나타냅니다.
.element {
/* Smooth deceleration - great for entrances */
transition: transform 0.3s cubic-bezier(0.0, 0.0, 0.2, 1.0);
/* Smooth acceleration - great for exits */
transition: transform 0.2s cubic-bezier(0.4, 0.0, 1, 1);
/* Material Design standard easing */
transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
}스텝 함수
steps() 함수는 부드러운 보간 대신 이산적인 프레임별 애니메이션을 만듭니다. 이는 스프라이트 애니메이션이나
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
.typing-effect {
animation: typing 3s steps(40, end);
overflow: hidden;
white-space: nowrap;
}성능 최적화
부드러운 애니메이션은 초당 60프레임(fps)을 달성해야 하며, 이는 각 프레임이 16.67밀리초 내에 완료되어야 함을 의미합니다. 이 예산을 충족하지 못하면 눈에 보이는 끊김 현상——화면 떨림, 프레임 누락 및 사용자 경험 저하가 발생합니다. 브라우저 렌더링 파이프라인을 이해하는 것이 고성능 애니메이션 작성의 핵심입니다.
렌더링 파이프라인
CSS 속성이 변경되면 브라우저는 디스플레이를 업데이트하기 위해 최대 세 단계를 거칩니다:
- 스타일: 어떤 CSS 규칙이 어떤 요소에 적용되는지 결정합니다.
- 레이아웃: 각 요소의 크기와 위치를 계산합니다. width, height, margin, padding, top, left, font-size 및 기타 기하학적 속성의 변경에 의해 트리거됩니다.
- 페인트: 각 요소의 픽셀을 채웁니다. color, background, box-shadow, border-radius 및 기타 시각적 속성의 변경에 의해 트리거됩니다.
- 합성: 페인트된 레이어를 올바른 순서로 결합합니다. 이 단계만 GPU에서 실행됩니다. transform과 opacity의 변경에 의해 트리거됩니다.
합성 단계만 트리거하는 애니메이션은 레이아웃과 페인트를 완전히 건너뛰기 때문에 성능이 가장 좋습니다. 이것이 transform과 opacity가 고성능 CSS 애니메이션의 황금 표준인 이유입니다.
합성기 전용 속성
| 속성 | 레이아웃 트리거? | 페인트 트리거? | GPU 합성? |
|---|---|---|---|
| transform | 아니요 | 아니요 | 예 |
| opacity | 아니요 | 아니요 | 예 |
| filter | 아니요 | 예 | RGB는 화면에서 사용하는 가산 색상 모델입니다. 다양한 강도로 빨간색, 초록색, 파란색 빛을 결합하여 가시 스펙트럼의 모든 색상을 만들어냅니다. 각 채널의 범위는 0(빛 없음)에서 255(최대 강도)까지입니다. 세 채널이 모두 0이면 검은색이 되고, 모두 255이면 흰색이 됩니다. 이 모델은 물리적 화면이 빛을 방출하여 색상을 생성하는 방식을 반영합니다. |
| top / left | 예 | 예 | 아니요 |
| width / height | 예 | 예 | 아니요 |
| margin / padding | 예 | 예 | 아니요 |
| background-color | 아니요 | 예 | 아니요 |
| box-shadow | 아니요 | 예 | 아니요 |
will-change 사용하기
will-change 속성은 브라우저에 어떤 속성이 변경될 예정인지 알려주어, 요소를 자체 합성기 레이어로 승격시켜 미리 최적화할 수 있게 합니다. 그러나 신중하게 사용해야 합니다:
/* Apply will-change just before the animation */
.element:hover {
will-change: transform;
}
/* Remove it after the animation completes */
.element {
transition: transform 0.3s ease;
}will-change의 일반적인 실수로는 너무 많은 요소에 적용하는 것(각 합성기 레이어가 GPU 메모리를 소비함)과 영구적으로 유지하는 것(리소스 낭비)이 있습니다. 좋은 방법은 애니메이션이 시작되기 전에 JavaScript로 will-change를 추가하고, 애니메이션이 끝난 후 제거하는 것입니다.
Transform으로 레이아웃 속성 대체하기
가장 효과적인 성능 최적화는 레이아웃을 트리거하는 속성을 transform으로 대체하는 것입니다:
- left: 100px 대신 transform: translateX(100px) 사용
- top: 50px 대신 transform: translateY(50px) 사용
- width: 150%; height: 150% 대신 transform: scale(1.5) 사용
- 레이아웃을 통한 회전 대신 transform: rotate(45deg) 사용
일반적인 애니메이션 패턴
특정 애니메이션 패턴은 웹 인터페이스에서 반복적으로 나타납니다. 이러한 패턴을 숙지하면 대부분의 일반적인 UI 애니메이션 요구 사항을 해결할 수 있는 도구 키트를 갖추게 됩니다.
1. 페이드 인
가장 간단하고 가장 다재다능한 진입 애니메이션입니다. 요소가 투명에서 불투명으로 전환됩니다:
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.4s ease-out forwards;
}2. 위로 슬라이드 페이드 인
수직 이동과 투명도 변화를 결합한 더 역동적인 진입 효과입니다. 요소가 아래에서 도달하는 느낌을 줍니다:
@keyframes slideUpFade {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-up {
animation: slideUpFade 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}3. 호버 시 확대
호버 시 미묘한 확대는 만족스러운 상호작용 피드백을 제공합니다:
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}4. 엇갈린 목록 애니메이션
항목 목록이 나타날 때 각 항목에 엇갈린 애니메이션 지연을 설정하면 시선을 유도하는 계단식 효과가 만들어집니다:
.list-item {
opacity: 0;
transform: translateY(10px);
animation: slideUpFade 0.3s ease-out forwards;
}
.list-item:nth-child(1) { animation-delay: 0.0s; }
.list-item:nth-child(2) { animation-delay: 0.05s; }
.list-item:nth-child(3) { animation-delay: 0.1s; }
.list-item:nth-child(4) { animation-delay: 0.15s; }
.list-item:nth-child(5) { animation-delay: 0.2s; }동적 목록의 경우, JavaScript를 사용하여 각 항목의 인덱스에 따라 animation-delay를 설정하거나, CSS 사용자 정의 속성을 사용하세요: style="animation-delay: calc(var(--i) * 0.05s)".
5. 로딩 스피너
부드러운 연속 회전은 고전적인 로딩 표시기입니다:
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 24px;
height: 24px;
border: 3px solid rgba(255, 255, 255, 0.2);
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}애니메이션 접근성: prefers-reduced-motion
모든 사용자가 애니메이션의 혜택을 받는 것은 아닙니다. 전정 장애, 운동 민감성 또는 특정 인지 상태를 가진 사람들에게 애니메이션은 어지러움, 메스꺼움 또는 발작을 유발할 수 있습니다. prefers-reduced-motion 미디어 쿼리를 사용하면 사용자의 시스템 수준 애니메이션 감소 선호도를 존중할 수 있습니다.
감소된 애니메이션 구현하기
권장되는 방법은 애니메이션 감소 선호도를 설정하지 않은 사용자에게만 애니메이션을 활성화하는 것입니다:
/* Default: no animation for users who prefer reduced motion */
.element {
opacity: 1;
transform: none;
}
/* Only animate for users who have no motion preference */
@media (prefers-reduced-motion: no-preference) {
.element {
animation: slideUpFade 0.5s ease-out forwards;
}
}또는, 애니메이션 감소를 선호하는 사용자를 위해 특정 애니메이션을 비활성화할 수 있습니다:
.element {
animation: slideUpFade 0.5s ease-out forwards;
}
@media (prefers-reduced-motion: reduce) {
.element {
animation: none;
opacity: 1;
transform: none;
}
/* Keep functional transitions but shorten duration */
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}무엇을 유지하고 무엇을 제거할까
사용자가 애니메이션 감소를 선호할 때 모든 애니메이션을 제거해서는 안 됩니다. 의미나 상태를 전달하는 기능적 애니메이션은 단순화하여 유지해야 합니다:
- 제거: 장식적 애니메이션, 시차 스크롤, 자동 재생 캐러셀, 펄스 효과 및 반복 배경 애니메이션.
- 단순화: 전환 지속 시간을 완전히 제거하는 대신 거의 순간적(0.01ms)으로 단축하여 상태 변화가 여전히 감지될 수 있도록 합니다.
- 유지: 기능적 상태 전환(호버, 포커스, 활성 상태), 진행 표시기 및 중요한 정보를 전달하는 모든 애니메이션.
애니메이션 라이브러리
CSS 애니메이션이 대부분의 UI 요구 사항을 처리하지만, JavaScript 애니메이션 라이브러리는 물리 기반 모션, 스크롤 연결 애니메이션 및 복잡한 시퀀스 편성과 같은 고급 기능을 제공합니다.
CSS 우선 라이브러리
- Animate.css: 미리 구축된 CSS 애니메이션 클래스 모음입니다. 요소에 클래스 이름만 추가하면 애니메이션을 적용할 수 있습니다. 빠른 프로토타이핑에 적합하지만, 과도하게 사용하면 진부한 효과로 이어질 수 있습니다.
- Hamburgers: 순수 CSS로 구현된 애니메이션 햄버거 메뉴 아이콘 전환 모음입니다.
JavaScript 애니메이션 라이브러리
- Framer Motion: 선언적 애니메이션 API를 제공하는 React 애니메이션 라이브러리로, 스프링 물리, 레이아웃 애니메이션 및 제스처 지원을 제공합니다. React 애플리케이션에 가장 적합한 선택입니다.
- GSAP (GreenSock): 가장 강력한 JavaScript 애니메이션 라이브러리입니다. 정밀한 타임라인 제어, 복잡한 이징, SVG 모핑 및 스크롤 트리거 애니메이션을 제공합니다. 대형 브랜드에서 마케팅 웹사이트와 인터랙티브 경험에 사용됩니다.
- Anime.js: 간결한 API를 가진 경량 JavaScript 애니메이션 라이브러리입니다. CSS 속성, SVG, DOM 속성 및 JavaScript 객체를 지원합니다. CSS와 GSAP 사이의 좋은 중간 선택입니다.
- Motion One: Web Animations API를 기반으로 구축된 최신 경량 애니메이션 라이브러리입니다. 최소화된 API를 제공하며, 스프링 물리와 타임라인을 지원합니다.
JavaScript vs CSS 사용 시기
CSS는 상태 변화에 반응하는 간단한 선언적 애니메이션에 사용합니다. JavaScript는 물리 기반 모션(스프링, 모멘텀), 스크롤 연결 애니메이션, 정밀한 시간 제어가 필요한 복잡한 시퀀스, 런타임에 계산되는 동적 애니메이션 매개변수, 또는 프로그래밍 방식으로 일시 정지, 반전 또는 드래그해야 하는 애니메이션에 사용합니다.
애니메이션 디버깅
애니메이션 버그는 미묘하고 진단하기 어려울 수 있습니다. 올바른 디버깅 도구와 기술은 프로세스를 더 효율적으로 만듭니다.
Chrome DevTools 애니메이션 패널
Chrome DevTools에는 강력한 디버깅 기능을 제공하는 전용 애니메이션 패널이 포함되어 있습니다:
- 타임라인 보기: 페이지의 모든 활성 애니메이션과 해당 지속 시간, 지연 및 키프레임을 확인합니다.
- 재생 제어: 애니메이션을 1/4 속도로 느리게 하거나, 일시 정지하거나, 타임라인을 프레임별로 드래그합니다.
- 속성 검사: 타임라인의 애니메이션을 클릭하여 애니메이션되는 정확한 CSS 속성과 각 키프레임에서의 값을 확인합니다.
- 실시간 편집: 페이지를 다시 로드하지 않고도 애니메이션 지속 시간, 지연 및 이징을 실시간으로 수정할 수 있습니다.
성능 분석
Chrome DevTools의 Performance 패널을 사용하여 애니메이션 끊김 현상을 식별하세요:
- 애니메이션이 실행되는 동안 성능 추적 기록하기
- 16.67ms 예산을 초과하는 프레임 찾기 (프레임 차트에서 빨간색 막대로 표시됨)
- 애니메이션 프레임에
- 'Rendering' 탭에서 'Paint flashing'(녹색으로 다시 그려지는 영역 표시)과 'Layer borders'(합성기 레이어 표시)를 활성화하세요
일반적인 디버깅 시나리오
- 애니메이션이 실행되지 않음: animation-fill-mode가 올바르게 설정되었는지 확인하세요. forwards나 both가 없으면 요소가 애니메이션 전 상태로 돌아갑니다. 또한 요소가 올바른 크기를 가지고 있는지 확인하세요 — transform: scale(0)은 요소를 보이지 않게 만들지만 여전히 존재합니다.
- 애니메이션 끊김: Performance 패널을 열어 긴 프레임을 찾으세요. 레이아웃을 트리거하는 속성에 애니메이션을 적용하고 있는지 확인하세요. transform과 opacity로 전환하세요.
- 애니메이션 지연이 이상하게 느껴짐: animation-delay는 애니메이션이 시작되기 전에 간격을 만듭니다. 이 간격 동안 animation-fill-mode: backwards 또는 both를 설정하지 않으면 요소가 애니메이션 전 상태를 표시합니다.
- 애니메이션이 Chrome에서는 작동하지만 Safari에서는 작동하지 않음: Safari는 animation 단축 속성의 구문 분석이 더 엄격합니다. 모든 값의 순서가 올바르고 지원되지 않는 기능을 사용하지 않았는지 확인하세요.
자주 묻는 질문
CSS 전환을 사용해야 할까요, CSS 애니메이션을 사용해야 할까요?
CSS 전환은 호버 효과, 포커스 상태 및 가시성 전환과 같은 A 지점에서 B 지점으로의 간단한 상태 변화에 사용됩니다. CSS 애니메이션(키프레임)은 복잡한 다단계 시퀀스, 반복 애니메이션 또는 상태 변화와 독립적으로 실행되어야 하는 애니메이션에 사용됩니다. 전환은 더 간단하고, 애니메이션은 더 많은 제어를 제공합니다.
성능을 보장하기 위해 안전하게 애니메이션할 수 있는 CSS 속성은 무엇인가요?
애니메이션 성능이 가장 좋은 속성은 transform과 opacity입니다. GPU 합성기에서 완전히 처리할 수 있어 레이아웃이나 페인트를 트리거하지 않기 때문입니다. width, height, margin, top과 같은 다른 속성은 레이아웃 재계산을 트리거하여 비용이 많이 듭니다. 항상 left 대신 transform: translateX()를, width/height 변경 대신 transform: scale()을 우선 사용하세요.
애니메이션에 민감한 사용자를 위한 접근성 지원 방법은?
prefers-reduced-motion 미디어 쿼리를 사용하여 운영 체제에서
will-change란 무엇이며 언제 사용해야 하나요?
will-change는 브라우저에 향후 어떤 속성이 변경될지 힌트를 주어 미리 최적화할 수 있게 하는 CSS 속성입니다. 신중하게 사용하고, 성능 문제가 관찰될 때만 사용하세요. 애니메이션 시작 전에 적용하고, 애니메이션 종료 후 제거하세요. will-change를 과도하게 사용하면 GPU 메모리를 낭비하고 실제로 성능을 저하시킬 수 있습니다.
CSS 애니메이션을 디버깅하는 방법은?
Chrome DevTools는 강력한 애니메이션 디버깅 도구를 제공합니다. 애니메이션 패널을 열어(더 많은 도구 메뉴에서) 모든 활성 애니메이션을 보고, 타임라인을 드래그하고, 지속 시간과 지연을 실시간으로 수정하고, 애니메이션을 1/4 속도로 느리게 할 수 있습니다. 또한 Performance 패널을 사용하여 애니메이션 끊김 현상과 레이아웃 스래싱을 식별할 수 있습니다.