ToolHub
View All Posts

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 속성은 애니메이션 실행 전후에 어떤 스타일이 적용되는지 제어합니다. 이는 가장 자주 오해되는 애니메이션 속성 중 하나입니다:

이징 함수

이징 함수(타이밍 함수라고도 함)는 애니메이션의 가속 곡선을 정의합니다. 애니메이션이 천천히 시작했다가 가속할지, 빠르게 시작했다가 감속할지, 아니면 더 복잡한 패턴을 따를지 결정합니다. 올바른 이징 함수는 애니메이션을 자연스럽게 느끼게 하고, 잘못된 이징 함수는 애니메이션을 기계적이거나 부자연스럽게 만듭니다.

내장 이징 함수

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 속성이 변경되면 브라우저는 디스플레이를 업데이트하기 위해 최대 세 단계를 거칩니다:

  1. 스타일: 어떤 CSS 규칙이 어떤 요소에 적용되는지 결정합니다.
  2. 레이아웃: 각 요소의 크기와 위치를 계산합니다. width, height, margin, padding, top, left, font-size 및 기타 기하학적 속성의 변경에 의해 트리거됩니다.
  3. 페인트: 각 요소의 픽셀을 채웁니다. color, background, box-shadow, border-radius 및 기타 시각적 속성의 변경에 의해 트리거됩니다.
  4. 합성: 페인트된 레이어를 올바른 순서로 결합합니다. 이 단계만 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으로 대체하는 것입니다:

성능 규칙: transform이나 opacity로 동일한 시각적 효과를 구현할 수 있다면, 레이아웃이나 페인트를 트리거하는 속성 대신 항상 이를 우선 사용하세요. 이 원칙 하나로 대부분의 애니메이션 성능 문제를 해결할 수 있습니다.

일반적인 애니메이션 패턴

특정 애니메이션 패턴은 웹 인터페이스에서 반복적으로 나타납니다. 이러한 패턴을 숙지하면 대부분의 일반적인 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;
  }
}

무엇을 유지하고 무엇을 제거할까

사용자가 애니메이션 감소를 선호할 때 모든 애니메이션을 제거해서는 안 됩니다. 의미나 상태를 전달하는 기능적 애니메이션은 단순화하여 유지해야 합니다:

애니메이션 라이브러리

CSS 애니메이션이 대부분의 UI 요구 사항을 처리하지만, JavaScript 애니메이션 라이브러리는 물리 기반 모션, 스크롤 연결 애니메이션 및 복잡한 시퀀스 편성과 같은 고급 기능을 제공합니다.

CSS 우선 라이브러리

JavaScript 애니메이션 라이브러리

JavaScript vs CSS 사용 시기

CSS는 상태 변화에 반응하는 간단한 선언적 애니메이션에 사용합니다. JavaScript는 물리 기반 모션(스프링, 모멘텀), 스크롤 연결 애니메이션, 정밀한 시간 제어가 필요한 복잡한 시퀀스, 런타임에 계산되는 동적 애니메이션 매개변수, 또는 프로그래밍 방식으로 일시 정지, 반전 또는 드래그해야 하는 애니메이션에 사용합니다.

애니메이션 디버깅

애니메이션 버그는 미묘하고 진단하기 어려울 수 있습니다. 올바른 디버깅 도구와 기술은 프로세스를 더 효율적으로 만듭니다.

Chrome DevTools 애니메이션 패널

Chrome DevTools에는 강력한 디버깅 기능을 제공하는 전용 애니메이션 패널이 포함되어 있습니다:

성능 분석

Chrome DevTools의 Performance 패널을 사용하여 애니메이션 끊김 현상을 식별하세요:

일반적인 디버깅 시나리오

CSS를 사용하고 계신가요? 프로덕션 환경을 위해 스타일시트를 서식 지정, 압축 및 최적화하는 무료 CSS 도구를 사용해 보세요.

CSS 서식 지정 도구CSS 압축 도구

자주 묻는 질문

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 패널을 사용하여 애니메이션 끊김 현상과 레이아웃 스래싱을 식별할 수 있습니다.