GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

CSS 키프레임 애니메이션 9종 — 한 줄 복붙으로

CSS 키프레임 애니메이션은 @keyframes로 만든 움직임을 클래스 하나에 담아 어떤 요소에든 그대로 붙여 쓰는 방식입니다. 뜨기·튀기·돌기·펄스·흔들기·비틀기·깜빡임·심장박동·젤리, 아홉 가지를 한 번에 모았고 전부 transform과 opacity 두 속성만 씁니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 이름 가나다순이 아니라 한 요소가 화면에서 맡는 역할 순서로 놓았습니다. 존재를 알리고(01~02), 대기 중임을 말하고(03~04), 주의를 끌고(05~06), 상태가 바뀌었음을 알립니다(07~09). 아래 표로 css 애니메이션 예제 아홉 개를 먼저 훑고, 각 항목 아래에서 코드를 확인하세요. 여기 아홉 개가 요소 전체를 움직이는 것과 달리 테두리 선만 도는 움직임은 CSS 테두리 애니메이션 9종에, 입력창에 포커스가 들어간 순간의 반응은 입력창 검색창 포커스 애니메이션에 따로 모아 두었습니다.

클래스 무엇이 움직이나 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 하나만 붙이면 요소가 위아래로 천천히 오갑니다.

둥둥 떠 있는
새 창에서 열기

01 · 둥둥 떠 있는 — 측정 누적면적 4.311% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateYease-in-out3초

어디에 붙이나

일러스트나 아이콘 배지처럼 화면 한구석에서 계속 존재를 알려야 하는 요소에 씁니다.

$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는 바닥에 튕기는 리듬으로 짧게 내려갔다 크게 튀어 오르길 반복합니다.

통통 튀는
새 창에서 열기

02 · 통통 튀는 — 측정 누적면적 0.997% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateY바운스 이징1초

눌러도 되는 자리 알리기

스크롤 유도 화살표나 "더 보기" 아이콘처럼 눌러도 되는 자리라는 걸 몸짓으로 알려야 할 때 씁니다.

$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은 로딩 스피너 전용이 아닙니다.

회전
새 창에서 열기

03 · 회전 — 측정 누적면적 0.233% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

rotatelinear1초

css 애니메이션 무한 반복을 붙일 수 있는 아이콘

새로고침 버튼이나 동기화 아이콘처럼 "지금 진행 중"을 말해야 하는 어떤 아이콘에도 그대로 붙일 수 있는 범용 회전 클래스입니다.

$duration: 1s;
$easing: linear;         // 가감속 없이 일정해야 끊겨 보이지 않는다

.a-spin { animation: spin $duration $easing infinite; }

@keyframes spin {
  to { transform: rotate(360deg); }
}

04펄스

레이아웃을 건드리지 않는 강조

.a-pulse는 scale과 opacity를 함께 오가며 레이아웃을 다시 그리지 않고도 시선을 붙잡습니다.

펄스
새 창에서 열기

04 · 펄스 — 측정 누적면적 7.199% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scaleopacity2초

라이브 점과 CTA 에

라이브 상태 점, 신규 기능 배지, 강조가 필요한 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는 폼 검증 실패 전용이 아닙니다. "이 요소를 봐 달라"는 범용 주의 환기용으로, 짧게 왕복하고 스스로 멈춥니다.

흔들리는
새 창에서 열기

05 · 흔들리는 — 측정 누적면적 0.845% · 움직인 프레임 6/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateX6회 왕복0.5초

튜토리얼에서 한 지점 짚기

새 기능 배지를 흔들거나 튜토리얼에서 한 지점을 짚을 때 씁니다.

$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비틀거림

05 흔들림과 무엇이 다른가

.a-wobble은 translateX와 rotate를 같은 keyframes 안에서 서로 다른 지점에 겹쳐 05 흔들림보다 불규칙하게 움직입니다.

비틀거림
새 창에서 열기

06 · 비틀거림 — 측정 누적면적 2.026% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateX+rotate다단 keyframes1초

드래그 가능한 카드에

드래그 가능한 카드나 재정렬되는 리스트 항목이 "저를 옮겨보세요"라고 말할 때 씁니다.

$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를 두 번 끊어 깜빡이되 요소를 완전히 지우지는 않습니다.

깜빡임
새 창에서 열기

07 · 깜빡임 — 측정 누적면적 0.15% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

opacity2단 점멸1초

긴급 공지 아이콘에

긴급 공지 아이콘, 라이브 방송 중 표시 점처럼 확실히 눈에 띄어야 하는 곳에 씁니다.

$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을 짧게 두 번 튀긴 뒤 쉬는 구간을 둬 실제 심장처럼 불균등한 리듬으로 뛰게 합니다.

심장박동
새 창에서 열기

08 · 심장박동 — 측정 누적면적 0.356% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scale 2단불균등 리듬1.2초

모니터링 대시보드 점에

좋아요 아이콘의 대기 상태나 실시간 모니터링 대시보드의 상태 점에 씁니다.

$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젤리처럼 출렁

scaleX 와 scaleY 를 반대로

.a-jelly는 scaleX와 scaleY를 서로 반대 방향으로 움직여 눌렸다 놓인 순간 젤리처럼 출렁이다 제 모양으로 돌아옵니다.

젤리처럼 출렁
새 창에서 열기

09 · 젤리처럼 출렁 — 측정 누적면적 7.007% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scale(1.25, .75) 어긋남젤리 이징0.6초

장바구니 담기 완료에

장바구니 담기 완료나 클릭 후 통통 튀는 버튼 피드백에 씁니다.

$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

fill-mode 가 필요한 때

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 펄스

아홉 개 중 04 펄스의 평균 강도(강도_평균)는 30.9로 가장 낮게 측정됐습니다. scale과 opacity를 미세하게만 움직이기 때문인데, 점처럼 작은 요소에 걸면 그 낮은 강도가 더 두드러져 거의 안 보일 수 있습니다. 버튼처럼 면적이 넓은 요소에 걸어야 은은하면서도 확실한 강조로 읽힙니다. 이 값들이 이미 들어간 아홉 개 파일의 압축 비밀번호는 nft4q6u2이고, 아래 버튼으로 받을 수 있습니다.

04 펄스처럼 강도가 낮아도 읽히는 움직임은 궤도가 반듯해서 가능한 것이고, 반대로 선 자체가 일부러 삐뚤빼뚤한 질감이 필요할 때도 있습니다. 같은 transform·opacity 두 속성만으로 그 결을 낸 예가 손그림 디자인 랜딩 UI 키트의 크레용 섹션 아홉 개입니다.

접근성

움직임만 끄고 정보는 남긴다

시스템에서 동작 줄이기를 켠 사용자를 위해 아홉 개 전부 @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 심장박동처럼 상태가 다른 것끼리는 같이 둬도 자연스럽습니다. 한 화면에서 아이콘 하나만 움직이게 하고 싶다면 요소 전체 대신 SVG 선을 움직이는 CSS 아이콘 애니메이션 9종 쪽이 덜 시끄럽습니다.

css 애니메이션 만들기에서 두 개를 한 요소에 겹치려면 어떻게 하나요?

클래스 두 개를 그냥 같이 붙이면 스타일시트에 나중 정의된 클래스가 animation 전체를 덮어써 하나만 남습니다. animation-name에 이름을 쉼표로 나열하고, 같은 속성을 건드린다면 animation-composition: add나 accumulate로 합치는 방식을 쓰세요. 자세한 예시는 함정 섹션에 있습니다.

React에서는 클래스 토글 대신 뭘 쓰나요?

zip의 react/ 폴더는 duration·easing·color를 props로 받아 CSS 변수로 넘깁니다. 05 흔들림과 09 젤리처럼 한 번만 재생하는 것은 useState로 상태 클래스를 켜고 onAnimationEnd에서 꺼서, 다시 클릭했을 때 재생되게 만들었습니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.