CSS 키프레임 애니메이션 9종 — 한 줄 복붙으로
CSS 키프레임 애니메이션은 @keyframes로 만든 움직임을 클래스 하나에 담아 어떤 요소에든 그대로 붙여 쓰는 방식입니다. 뜨기·튀기·돌기·펄스·흔들기·비틀기·깜빡임·심장박동·젤리, 아홉 가지를 한 번에 모았고 전부 transform과 opacity 두 속성만 씁니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
아홉 개는 이름 가나다순이 아니라 한 요소가 화면에서 맡는 역할 순서로 놓았습니다. 존재를 알리고(01~02), 대기 중임을 말하고(03~04), 주의를 끌고(05~06), 상태가 바뀌었음을 알립니다(07~09). 아래 표로 먼저 훑고, 각 항목 아래에서 코드를 확인하세요.
| 클래스 | 무엇이 움직이나 | duration | 반복 |
|---|---|---|---|
.a-float |
translateY | 3초 | 무한 |
.a-bounce |
translateY | 1초 | 무한 |
.a-spin |
rotate | 1초 | 무한 |
.a-pulse |
scale·opacity | 2초 | 무한 |
.a-shake |
translateX | 0.5초 | 한 번(트리거) |
.a-wobble |
translateX·rotate | 1초 | 무한 |
.a-flash |
opacity | 1초 | 무한 |
.a-heartbeat |
scale | 1.2초 | 무한 |
.a-jelly |
scaleX·scaleY | 0.6초 | 한 번(트리거) |
01둥둥 떠 있는
.a-float 하나만 붙이면 요소가 위아래로 천천히 오갑니다. 일러스트나 아이콘 배지처럼 화면 한구석에서 계속 존재를 알려야 하는 요소에 씁니다.
$duration: 3s;
$easing: ease-in-out;
.a-float { animation: float $duration $easing infinite; }
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-16px); }
}
02통통 튀는
.a-bounce는 바닥에 튕기는 리듬으로 짧게 내려갔다 크게 튀어 오르길 반복합니다. 스크롤 유도 화살표나 "더 보기" 아이콘처럼 눌러도 되는 자리라는 걸 몸짓으로 알려야 할 때 씁니다.
$duration: 1s;
$easing: cubic-bezier(.28, .84, .42, 1);
.a-bounce { animation: bounce $duration $easing infinite; }
@keyframes bounce {
0%, 100% { transform: translateY(0); }
30% { transform: translateY(-4px); }
55% { transform: translateY(10px); }
75% { transform: translateY(-2px); }
90% { transform: translateY(4px); }
}
03회전
.a-spin은 로딩 스피너 전용이 아닙니다. 새로고침 버튼이나 동기화 아이콘처럼 "지금 진행 중"을 말해야 하는 어떤 아이콘에도 그대로 붙일 수 있는 범용 회전 클래스입니다.
$duration: 1s;
$easing: linear; // 가감속 없이 일정해야 끊겨 보이지 않는다
.a-spin { animation: spin $duration $easing infinite; }
@keyframes spin {
to { transform: rotate(360deg); }
}
04펄스
.a-pulse는 scale과 opacity를 함께 오가며 레이아웃을 다시 그리지 않고도 시선을 붙잡습니다. 라이브 상태 점, 신규 기능 배지, 강조가 필요한 CTA 버튼에 씁니다.
$duration: 2s;
$easing: ease-out;
.a-pulse { animation: pulse $duration $easing infinite; }
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.06); opacity: .92; }
}
05흔들리는
.a-shake는 폼 검증 실패 전용이 아닙니다. "이 요소를 봐 달라"는 범용 주의 환기용으로, 짧게 왕복하고 스스로 멈춥니다. 새 기능 배지를 흔들거나 튜토리얼에서 한 지점을 짚을 때 씁니다.
$duration: 500ms;
$easing: cubic-bezier(.36, .07, .19, .97);
$amount: 8px;
.a-shake.is-shaking { animation: shake $duration $easing 1; }
@keyframes shake {
10%, 90% { transform: translateX($amount * -.5); }
20%, 80% { transform: translateX($amount); }
30%, 70% { transform: translateX($amount * -1); }
40%, 60% { transform: translateX($amount * .7); }
50% { transform: translateX($amount * -.7); }
}
06비틀거림
.a-wobble은 translateX와 rotate를 같은 keyframes 안에서 서로 다른 지점에 겹쳐 05 흔들림보다 불규칙하게 움직입니다. 드래그 가능한 카드나 재정렬되는 리스트 항목이 "저를 옮겨보세요"라고 말할 때 씁니다.
$duration: 1s;
$easing: ease-in-out;
.a-wobble { animation: wobble $duration $easing infinite; }
@keyframes wobble {
0%, 100% { transform: translateX(0) rotate(0deg); }
15% { transform: translateX(-6px) rotate(-4deg); }
30% { transform: translateX(5px) rotate(3deg); }
45% { transform: translateX(-4px) rotate(-2deg); }
60% { transform: translateX(3px) rotate(2deg); }
75% { transform: translateX(-2px) rotate(-1deg); }
}
07깜빡임
.a-flash는 opacity를 두 번 끊어 깜빡이되 요소를 완전히 지우지는 않습니다. 긴급 공지 아이콘, 라이브 방송 중 표시 점처럼 확실히 눈에 띄어야 하는 곳에 씁니다.
$duration: 1s;
$easing: ease-in-out;
.a-flash { animation: flash $duration $easing infinite; }
@keyframes flash {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: .15; }
}
08심장박동
.a-heartbeat는 scale을 짧게 두 번 튀긴 뒤 쉬는 구간을 둬 실제 심장처럼 불균등한 리듬으로 뛰게 합니다. 좋아요 아이콘의 대기 상태나 실시간 모니터링 대시보드의 상태 점에 씁니다.
$duration: 1.2s;
$easing: ease-in-out;
.a-heartbeat { animation: heartbeat $duration $easing infinite; }
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.24); }
28% { transform: scale(1); }
42% { transform: scale(1.18); }
70% { transform: scale(1); }
}
09젤리처럼 출렁
.a-jelly는 scaleX와 scaleY를 서로 반대 방향으로 움직여 눌렸다 놓인 순간 젤리처럼 출렁이다 제 모양으로 돌아옵니다. 장바구니 담기 완료나 클릭 후 통통 튀는 버튼 피드백에 씁니다.
$duration: 600ms;
$easing: cubic-bezier(.68, -0.55, .27, 1.55);
.a-jelly.is-jellying { animation: jelly $duration $easing 1; }
@keyframes jelly {
0% { transform: scale(1, 1); }
20% { transform: scale(1.25, .75); }
40% { transform: scale(.75, 1.25); }
60% { transform: scale(1.15, .85); }
80% { transform: scale(.95, 1.05); }
100% { transform: scale(1, 1); }
}
animation 축약형은 왜 8개 값이 이 순서인가
animation: 한 줄에 값을 몰아 쓸 때 순서를 외우지 않으면 duration과 delay를 헷갈리기 쉽습니다. MDN의 animation 단축 속성 문서가 정리한 공식 순서는 이렇습니다. animation-name은 다른 값과 겹칠 일이 없어 이 편의 코드처럼 맨 앞에 써도 그대로 파싱되지만, MDN은 모호함을 피하려면 맨 뒤에 두길 권장합니다.
| 자리 | 하위 속성 | 이 편의 예 | 기본값 |
|---|---|---|---|
| 1 | animation-duration |
3s |
0s |
| 2 | animation-timing-function |
ease-in-out |
ease |
| 3 | animation-delay |
(없으면 0) | 0s |
| 4 | animation-iteration-count |
infinite |
1 |
| 5 | animation-direction |
(없으면 normal) | normal |
| 6 | animation-fill-mode |
(없으면 none) | none |
| 7 | animation-play-state |
(없으면 running) | running |
| 8 | animation-name |
float |
none |
animation-fill-mode는 애니메이션이 뛰기 전·뛴 뒤에도 keyframes 값을 유지할지 정합니다. 05 흔들림과 09 젤리는 1(한 번만 재생)로 끝나는데, 0%와 100%가 둘 다 원래 모양이라 fill-mode 없이도 자연스럽게 제자리로 돌아옵니다. 만약 keyframes의 마지막 프레임이 원래 모습과 다르다면 forwards를 더해야 애니메이션이 끝난 뒤에도 그 모양이 유지됩니다.
어디서 깨지는가 — 함정 둘
.a-float와 .a-spin처럼 서로 다른 유틸리티 클래스를 같은 요소에 동시에 붙이면 둘 다 animation 축약 속성을 통째로 선언하기 때문에, CSS 캐스케이드 규칙에 따라 스타일시트에 나중에 정의된 규칙이 앞의 것을 완전히 덮어써 하나만 움직입니다. (class 속성에 적는 순서는 상관없습니다.) 한 요소에 두 움직임을 겹치려면 animation-name: float, spin;처럼 이름을 나열하고 animation-duration·animation-timing-function도 같은 개수로 나열해야 합니다. 이때 두 애니메이션이 같은 속성(transform)을 건드리면 기본값 replace가 서로를 덮어써 결국 하나만 보이는데, animation-composition: add를 주면 두 transform 값을 더해 함께 적용합니다. 이 속성은 2023년 7월부터 주요 브라우저에 널리 퍼졌습니다.
아홉 개 중 04 펄스의 평균 강도(강도_평균)는 30.9로 가장 낮게 측정됐습니다. scale과 opacity를 미세하게만 움직이기 때문인데, 점처럼 작은 요소에 걸면 그 낮은 강도가 더 두드러져 거의 안 보일 수 있습니다. 버튼처럼 면적이 넓은 요소에 걸어야 은은하면서도 확실한 강조로 읽힙니다. 이 값들이 이미 들어간 아홉 개 파일의 압축 비밀번호는 nft4q6u2이고, 아래 버튼으로 받을 수 있습니다.
접근성
시스템에서 동작 줄이기를 켠 사용자를 위해 아홉 개 전부 @media (prefers-reduced-motion: reduce) 안에서 animation: none으로 끕니다. 움직임을 지우되 정보는 남기는 게 원칙이라, 05 흔들림은 배경색으로 대신 알리고 나머지는 애니메이션이 멈춘 정적 상태 그대로 읽을 수 있습니다. 이 미디어 쿼리를 어떤 브라우저가 읽는지는 MDN 문서의 호환성 표에 정리돼 있습니다.
@media (prefers-reduced-motion: reduce) {
.a-float, .a-bounce, .a-spin, .a-pulse,
.a-wobble, .a-flash, .a-heartbeat { animation: none; }
.a-shake.is-shaking, .a-jelly.is-jellying { animation: none; }
}
다른 CSS 애니메이션 글은 CSS 애니메이션 카테고리에 모여 있고, 이 사이트가 무엇을 다루는지는 소개에 있습니다.
FAQ
아홉 개를 한 화면에 다 써도 되나요?
넣을 수는 있지만 한 화면에는 두세 개 이하가 좋습니다. 04 펄스와 07 깜빡임처럼 둘 다 "주목하라"는 신호를 가진 클래스를 한 화면에 여러 개 두면 어느 것도 특별해 보이지 않습니다. 03 회전과 08 심장박동처럼 상태가 다른 것끼리는 같이 둬도 자연스럽습니다.
두 애니메이션을 한 요소에 겹치려면 어떻게 하나요?
클래스 두 개를 그냥 같이 붙이면 스타일시트에 나중 정의된 클래스가 animation 전체를 덮어써 하나만 남습니다. animation-name에 이름을 쉼표로 나열하고, 같은 속성을 건드린다면 animation-composition: add나 accumulate로 합치는 방식을 쓰세요. 자세한 예시는 함정 섹션에 있습니다.
React에서는 클래스 토글 대신 뭘 쓰나요?
zip의 react/ 폴더는 duration·easing·color를 props로 받아 CSS 변수로 넘깁니다. 05 흔들림과 09 젤리처럼 한 번만 재생하는 것은 useState로 상태 클래스를 켜고 onAnimationEnd에서 꺼서, 다시 클릭했을 때 재생되게 만들었습니다.