한 줄 정답@keyframes 이름 { 0% {...} 100% {...} }으로 변화 구간을 정의하고, 움직일 요소에 animation: 이름 시간 ...;을 걸면 재생된다. 예: @keyframes fade { from { opacity: 0; } to { opacity: 1; } } + .box { animation: fade 0.4s ease; }. transition이 상태가 바뀔 때만 동작한다면, animation트리거 없이 스스로 시작하고 무한 반복도 된다.

핵심 요약#

  • 두 단계다: @keyframes로 구간을 정의하고, 요소에 animation으로 그 구간을 재생한다.
  • @keyframesfrom/to 또는 0%~100%로 중간 지점까지 마음대로 끊을 수 있다.
  • animation 단축에는 여덟 속성이 들어간다: name · duration · timing-function · delay · iteration-count · direction · fill-mode · play-state.
  • 무한 반복은 animation-iteration-count: infinite, 끝 상태 유지는 animation-fill-mode: forwards다.
  • transition은 hover 같은 상태 변화에 반응할 때, animation스스로 움직이거나 반복할 때 쓴다.

CSS 애니메이션 keyframes 사용법, 두 단계로 나눠 보기#

CSS 애니메이션은 늘 두 조각으로 이뤄진다. 먼저 무엇이 어떻게 변할지@keyframes에 적어 두고, 그 변화를 어떤 요소에 얼마 동안 재생할지animation 속성으로 지정한다. 이름표 하나로 둘을 잇는 구조다.

transition과 가장 큰 차이는 트리거다. transition:hover나 클래스 토글처럼 값이 바뀌는 순간에만 동작한다. animation은 요소가 화면에 그려지는 순간 스스로 시작하고, 원하면 끝없이 반복한다. 로딩 스피너, 깜빡임, 자동 등장 효과가 모두 animation의 영역이다.

/* 1단계: 변화 구간을 정의 */
@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* 2단계: 요소에 재생 */
.box {
  animation: fade-in 0.4s ease;
}

@keyframes — 변화의 구간을 그린다#

@keyframes 뒤에 이름을 붙이고, 중괄호 안에 시점별 상태를 적는다. 시작과 끝만 필요하면 from/to, 중간을 끊고 싶으면 백분율을 쓴다.

@keyframes pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.15); } /* 중간에 가장 커짐 */
  100% { transform: scale(1); }
}

from0%, to100%와 같다. 적지 않은 시점은 브라우저가 앞뒤 값을 보고 알아서 보간한다. 같은 속성을 여러 구간에 흩어 적으면 그 속성이 그 흐름대로 변한다.

animation — 정의한 구간을 재생한다#

요소 쪽에는 어떤 keyframes를, 얼마 동안, 어떻게 재생할지를 적는다. 최소한 이름과 시간(duration) 두 가지는 있어야 움직인다. duration이 없으면 기본이 0s라 아무것도 보이지 않는데, 이는 애니메이션이 "안 먹는" 가장 흔한 원인이다.

.loader {
  animation-name: pulse;
  animation-duration: 1s;
  animation-iteration-count: infinite; /* 무한 반복 */
}

animation의 여덟 속성 정리#

animation은 여덟 개의 하위 속성을 한 줄로 묶은 단축이다. 각각이 무엇을 정하는지 알아야 단축을 자유롭게 쓸 수 있다.

속성정하는 것자주 쓰는 값
animation-name재생할 @keyframes 이름직접 지은 이름
animation-duration한 번 도는 데 걸리는 시간0.4s, 1s
animation-timing-function속도 곡선ease, linear, ease-out
animation-delay시작까지 기다리는 시간0s, 0.2s
animation-iteration-count몇 번 반복할지숫자, infinite
animation-direction반복 방향normal, alternate
animation-fill-mode재생 전후로 어떤 값을 유지할지forwards, both
animation-play-state재생/일시정지running, paused

iteration-count와 direction — 반복을 다룬다#

iteration-count는 반복 횟수다. 숫자로 적으면 그만큼, infinite면 무한히 돈다. direction은 반복할 때 방향인데, alternate로 두면 갔다가 되돌아오며 자연스럽게 왕복한다.

.heartbeat {
  animation: pulse 1.2s ease-in-out infinite alternate;
}

infinite alternate 조합은 0% → 100%로 갔다가 다시 100% → 0%로 돌아오므로, keyframes에 끝 구간을 따로 적지 않아도 매끄럽게 반복된다.

fill-mode — 끝난 뒤 상태를 정한다#

기본적으로 애니메이션은 끝나면 원래(CSS에 적힌) 값으로 되돌아간다. 등장 효과를 만들었는데 끝나자마자 다시 사라지는 건 이 때문이다. animation-fill-mode: forwards를 주면 마지막 키프레임 상태를 그대로 유지한다.

@keyframes slide-up {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.card {
  animation: slide-up 0.5s ease-out forwards; /* 끝난 자리에 멈춤 */
}
fill-mode효과
none (기본)재생 전후 모두 원래 값
forwards끝난 뒤 마지막 키프레임 값 유지
backwardsdelay 동안 첫 키프레임 값을 미리 적용
bothforwards + backwards 둘 다

단계별로 따라하기 — 자동 등장 카드 만들기#

스크롤하다 나타나는 듯한, 가장 많이 쓰는 "아래에서 떠오르며 페이드인" 패턴이다.

  1. @keyframes에 **시작(투명·아래)**과 **끝(불투명·제자리)**을 정의한다.
  2. 요소에 animation으로 이름·시간·곡선을 건다.
  3. 끝난 상태를 유지하도록 forwards를 붙인다.
  4. 움직임은 top·margin 대신 transform으로 준다(부드럽고 성능에 유리).
@keyframes slide-up {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card {
  animation: slide-up 0.5s ease-out forwards;
}

/* 여러 카드를 시차를 두고 등장시키기 */
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }

animation-delay를 카드마다 조금씩 늘리면 순차로 등장하는 효과가 난다. 카드들을 가로로 늘어놓는 배치는 CSS flexbox 정렬 — 주축·교차축으로 한 번에 이해하기와 함께 보면 레이아웃과 움직임을 같이 잡을 수 있다.

transition과 animation, 언제 무엇을 쓸까#

둘 다 시간에 걸쳐 값을 바꾸지만 쓰임이 다르다. 헷갈릴 땐 트리거가 있느냐로 가른다.

기준transitionanimation
시작 방식상태 변화(hover·focus·클래스 토글)스스로 시작
중간 구간시작·끝 두 값만@keyframes로 여러 구간
반복불가(왕복은 상태 왕복으로만)infinite로 무한 반복
적합한 곳버튼 hover, 펼침/접힘스피너, 깜빡임, 자동 등장

버튼에 마우스를 올렸을 때 색을 바꾸는 정도라면 transition이 더 단순하고 적합하다. 그 기본기는 CSS transition 사용법 — hover 애니메이션 부드럽게 만들기에 따로 정리해 두었다. 반대로 트리거 없이 계속 돌거나, 여러 구간을 거치는 복잡한 움직임이면 animation이 맞다.

성능과 접근성 — 빠뜨리기 쉬운 두 가지#

성능: 움직임을 줄 때 left·top·width·height를 애니메이션하면 매 프레임 레이아웃이 다시 계산돼 끊길 수 있다. 가능하면 transformopacity 애니메이션한다. 이 둘은 레이아웃을 건드리지 않아 부드럽게 도는데, 위치를 옮기는 무한 애니메이션일수록 차이가 크다.

접근성: 화면이 계속 움직이면 어지럼증·전정기관 질환이 있는 사용자에게 불편하거나 위험할 수 있다. 운영체제에서 "동작 줄이기"를 켠 사용자를 위해 prefers-reduced-motion 미디어 쿼리로 애니메이션을 약하게 하거나 끈다.

@media (prefers-reduced-motion: reduce) {
  .card,
  .loader {
    animation: none;
  }
}

CSS 애니메이션이 안 움직일 때 — 흔한 원인#

대부분 다음 중 하나다.

  • duration이 없다. animation에 시간을 안 적으면 기본 0s라 즉시 끝나 보이지 않는다. 가장 흔한 실수다.
  • @keyframes 이름과 animation-name이 다르다. 오타 하나면 재생할 구간을 못 찾아 아무 일도 일어나지 않는다.
  • 끝난 뒤 원래대로 돌아간다. 등장 효과가 다시 사라진다면 animation-fill-mode: forwards가 빠진 것이다.
  • 애니메이션되지 않는 속성을 바꿨다. display 같은 값은 보간되지 않는다. opacity로 우회한다.
  • 요소가 화면에 없다. display: none인 요소는 애니메이션이 시작되지 않는다.

자주 묻는 질문#

Q. keyframes는 어떻게 사용하나요?

A. 두 단계입니다. 먼저 @keyframes 이름 { from {...} to {...} }(또는 0%~100%)로 변화 구간을 정의하고, 움직일 요소에 animation: 이름 시간;을 걸어 재생합니다. 이름표가 둘을 잇는 핵심이라 @keyframes의 이름과 animation-name이 정확히 같아야 합니다.

Q. transition과 animation은 무엇이 다른가요?

A. transition은 hover·클래스 토글처럼 상태가 바뀌는 순간에만 동작하고 시작·끝 두 값만 다룹니다. animation은 트리거 없이 스스로 시작하며 @keyframes로 여러 구간을 거치고 infinite로 무한 반복할 수 있습니다. 버튼 hover는 transition, 스피너·자동 등장은 animation이 맞습니다.

Q. CSS로 무한 반복 애니메이션은 어떻게 만드나요?

A. animation-iteration-count: infinite를 주면 됩니다. 단축으로는 animation: pulse 1s ease-in-out infinite처럼 적습니다. 갔다가 되돌아오며 왕복시키려면 infinite alternate를 함께 씁니다.

Q. 애니메이션이 끝나면 원래대로 돌아가는데 끝 상태를 유지하려면요?

A. animation-fill-mode: forwards를 주면 마지막 키프레임 값을 그대로 유지합니다. 기본값 none은 재생이 끝나면 CSS에 적힌 원래 값으로 되돌아가기 때문에 등장 효과가 다시 사라집니다. delay 동안 첫 키프레임을 미리 적용하려면 both를 씁니다.

관련 글#

애니메이션은 결국 "시간에 걸쳐 값이 변하는 것"이다. 상태 변화에 반응하는 transition을 먼저 익히고, 배치(레이아웃)와 함께 보면 화면에 자연스러운 움직임을 입히기 쉬워진다.

더 알아보기#