예제 하나로 보는 CSS 애니메이션
애니메이션은 두 부분으로 되어 있어요. 단계를 설명하는 @keyframes, 그리고 지속 시간과 함께 요소에 적용하는 animation 속성이에요. 이 카드는 페이지가 로드되면 미끄러지면서 나타나요.
코드를 고치면(또는 지속 시간을 2s로 바꾸면) 미리보기가 다시 로드되어 카드가 다시 들어오는 걸 볼 수 있어요. @media (prefers-reduced-motion: reduce) 블록은 시스템이 동작 줄이기로 설정된 독자에게만 키프레임을 페이드로 바꿔요.
@keyframes 문법
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.08); }
100% { transform: scale(1); }
}
from은0%,to는100%와 같아요.- 퍼센트 단계를 필요한 만큼 추가하세요. 스타일이 같은 단계는 한 줄을 공유할 수 있어요.
0%, 100% { ... } - 첫 키프레임이나 마지막 키프레임에 없는 속성은 요소 자신의 값에서부터, 또는 그 값까지 애니메이션돼요.
- 이름(
pulse)은animation-name에 넘기는 값이에요. 대소문자를 구분하고,none같은 키워드면 안 돼요.
animation 속성
| 속성 | 정하는 것 | 기본값 |
|---|---|---|
animation-name | 실행할 @keyframes | none |
animation-duration | 한 주기의 길이 | 0s |
animation-timing-function | 키프레임 사이의 속도 곡선 | ease |
animation-delay | 첫 주기 전 대기(음수면 중간부터 시작) | 0s |
animation-iteration-count | 주기 수, 또는 infinite | 1 |
animation-direction | normal, reverse, alternate, alternate-reverse | normal |
animation-fill-mode | 전후 스타일: none, forwards, backwards, both | none |
animation-play-state | running 또는 paused | running |
단축 속성은 거의 어떤 순서로든 받아요. 유일한 규칙은 첫 번째 시간 값이 지속 시간이고 두 번째가 지연이라는 거예요.
.loader {
animation: pulse 1.2s ease-in-out 0.2s infinite alternate both;
/* name duration timing delay count direction fill */
}
direction과 fill-mode 실시간으로 보기
direction, fill mode, 횟수를 고르고 Play를 누르세요. 상자가 트랙 끝까지 200px 움직여요. 표시 창은 애니메이션이 끝난 뒤 상자가 어디서 멈췄는지 알려 줘요.
볼 것들이에요.
fill-mode: none(기본값): 마지막 프레임 뒤에 상자가 x = 0으로 튀어 돌아가고 다시 주황색이 돼요.forwards: 마지막 키프레임에 머물러요.normal이면 x = 200에 초록색이고,reverse면 마지막으로 재생된 키프레임이from이라서 x = 0에 파란색으로 끝나요.- 횟수 2의
alternate는 갔다가 돌아와서,forwards여도 x = 0에서 끝나요.
무한 반복: 로딩 표시
infinite는 영원히 반복하고, 자식마다 animation-delay를 주면 차례로 움직이도록 엇갈려요. 반복은 차분하게 유지하세요. 느리게, 작게, 깜빡임 없이요.
aria-label이 있는 role="status"는 스크린 리더 사용자에게 무언가 로딩 중이라고 알려 줘요. 점만으로는 알 수 없어요. 회전하는 원은 회전 페이지를 보세요.
애니메이션 일시정지와 제어
animation-play-state: paused는 애니메이션을 그 자리에 멈추고, running은 거기서 이어가요. 아래 막대에 마우스를 올리면 멈춰요.
JavaScript에서 element.getAnimations()는 실행 중인 CSS 애니메이션을 돌려주고, 각각 pause(), play(), cancel()과 읽거나 지정할 수 있는 currentTime을 가져요. animationend 이벤트는 무한이 아닌 애니메이션이 끝날 때, animationiteration은 각 주기가 끝날 때 발생해요.
움직임 줄이기를 존중하고, 절대 깜빡이지 마세요
모든 애니메이션에 적용되는 접근성 규칙이 두 가지 있어요.
- 움직임 줄이기. 움직임 때문에 몸이 불편해져서 시스템에 움직임을 줄여 달라고 요청하는 사람들이 있어요. 가장 안전한 패턴은 그런 요청이 없을 때만 움직임을 더하는 거예요.
@media (prefers-reduced-motion: no-preference) {
.hero-title { animation: slide-in 0.8s ease-out both; }
}
- 깜빡임 금지. 1초에 세 번 넘게 깜빡이는 콘텐츠는 발작을 일으킬 수 있어요. 빠르고 대비가 큰 깜빡임은 아예 피하세요.
애니메이션을 부드럽게 유지하기
가능하면 transform과 opacity를 애니메이션하세요. 브라우저가 페이지 레이아웃을 다시 하지 않고 적용할 수 있어서 느린 휴대폰에서도 부드러워요. width, height, top, left, margin을 애니메이션하면 매 프레임 레이아웃을 다시 계산해요. 요소를 옮길 때는 transform 페이지의 translate()를 쓰세요.
자주 하는 실수
- 지속 시간 없음. 기본 지속 시간이
0s라서animation: fade;는 아무것도 하지 않아요. - 일치하지 않는 이름.
animation-name은 대소문자까지@keyframes이름과 정확히 같아야 해요. - 끝에서 튀어 돌아가기. 마지막 키프레임을 유지하려면
forwards(또는both)를 추가하세요. - 지연된 애니메이션이 시작되기 전의 깜빡임.
animation-delay동안 요소는 원래 스타일을 보여 줘요.backwards(또는both)가 지연 동안 첫 키프레임을 적용해요. - 같은 값을 지정해서 애니메이션 재시작하기. 같은
animation을 다시 지정해도 아무 일도 없어요. 위의 Play 버튼처럼 지우고, 리플로우를 강제하고(el.offsetWidth), 다시 지정하세요. display애니메이션하기. 즉시 바뀌어요. 대신opacity와transform을 애니메이션하세요.
자주 묻는 질문
CSS로 애니메이션은 어떻게 만드나요?
@keyframes name { from { ... } to { ... } }로 단계를 정의하고, animation 속성과 지속 시간으로 요소에 적용해요. animation: name 1s ease-out; 지속 시간의 기본값이 0s라서 지속 시간은 필수예요.
animation-fill-mode: forwards는 무엇을 하나요?
애니메이션이 끝난 뒤에도 마지막 키프레임의 스타일을 유지해요. 없으면 요소가 원래 스타일로 튀어 돌아가요. backwards는 지연 시간 동안 첫 키프레임을 적용하고, both는 둘 다 해요.
CSS 애니메이션을 무한 반복하려면 어떻게 하나요?
animation-iteration-count: infinite를 지정하거나 단축 속성에 infinite를 추가하세요. animation: spin 1s linear infinite; alternate를 추가하면 주기마다 앞으로 갔다가 뒤로 재생해요.
CSS animation과 transition은 무엇이 다른가요?
transition은 보통 hover나 클래스 변경으로 속성이 바뀔 때 두 값 사이를 애니메이션해요. animation은 적용되자마자 스스로 실행되고, 키프레임을 몇 개든 가질 수 있으며, 반복, 역재생, 일시정지를 할 수 있어요.
CSS 애니메이션은 어떻게 일시정지하나요?
요소에 animation-play-state: paused를 지정하세요. 예를 들어 :hover나 JavaScript가 추가하는 클래스에서요. 다시 running으로 지정하면 멈춘 곳에서 이어져요.
움직임 줄이기를 선호하는 사용자에게 애니메이션을 끄려면 어떻게 하나요?
애니메이션을 @media (prefers-reduced-motion: no-preference) { ... }로 감싸거나, @media (prefers-reduced-motion: reduce) { ... } 안에서 제거하세요. 이 쿼리는 사용자 운영체제의 동작 줄이기 설정을 따라요.