GODRICH

CSS 통통 튀는 애니메이션 9종 — 한 줄 복붙으로

CSS 통통 튀는 애니메이션은 요소를 그냥 나타나게 하지 않고 뜨고, 튕기고, 떨고, 흔들리게 만들어 살아 있는 느낌을 주는 움직임입니다. 흔한 영어 이름 대신 둥실둥실·포용·푸루푸루 같은 의성어로 아홉 개를 골랐고, 그중 두 개(포용·스르륵)는 실제로 마우스를 올려야 움직이는 진짜

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

아홉 개는 이름 가나다순이 아니라 버튼 하나가 화면에서 밟는 순서로 놓았습니다. 존재를 알리고(01), 눌러 달라고 조르고(02~03), 옆에서 조용히 숨 쉬고(04~05), 더 보라고 손짓하고(06), 뒤에서 배경을 꾸미고(07), 잘 골랐다고 반짝이고(08), 다음 화면으로 미끄러지듯 넘어갑니다(09). 표에서 duration과 트리거만 먼저 훑고, 각 항목 섹션에서 실제 코드를 열어 보십시오.

클래스 무엇이 움직이나 duration 트리거
.o-float translateY·rotate 2.8s 상시
.o-poyon scale 550ms 호버·포커스
.o-puru scaleX·scaleY 900ms 상시
.o-yura rotate 2.4s 상시
.o-kuru rotate 1.6s 상시
.o-pyon translateY·scaleY 900ms 상시
.o-gunya border-radius 3.2s 상시
.o-kira opacity·scale 1.8s 상시
.o-nuru translateX·scale 700ms 호버·포커스

01둥실둥실 뜨는(ふわふわ)

.o-float 하나만 붙이면 배지가 translateY와 약한 rotate로 천천히 오르내립니다. 신상품 배지나 프로모션 알림 칩처럼 화면 한구석에서 계속 존재를 알려야 하는 요소에 씁니다.

translateYrotate 3deg2.8s
$duration: 2.8s;
$easing: ease-in-out;
$color: $subject-blue;

.o-float {
  animation: o-float $duration $easing infinite;
}

@keyframes o-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-14px) rotate(-3deg); }
}

02포용 튕기는(ぽよん)

.o-poyon은 마우스를 올리거나 키보드로 포커스하면 scale이 눌렸다 크게 튕기는 elastic 이징으로 한 번 움직입니다. 장바구니 담기나 좋아요처럼 누르고 싶게 만들어야 하는 버튼에 씁니다.

scale overshootelastic550ms
$duration: 550ms;
$easing: cubic-bezier(.34, 1.56, .64, 1);
$color: $subject-cream;

.btn.o-poyon:hover,
.btn.o-poyon:focus-visible { animation: o-poyon $duration $easing 1; }

@keyframes o-poyon {
  0%   { transform: scale(1); }
  30%  { transform: scale(.88); }
  55%  { transform: scale(1.16); }
  75%  { transform: scale(.95); }
  100% { transform: scale(1); }
}

03푸루푸루 젤리(ぷるぷる)

.o-puru는 scaleX·scaleY를 아주 작은 폭으로 서로 어긋나게 반복합니다. 찜하기 하트나 알림 종 아이콘이 탭해 달라는 듯 계속 미세하게 떠는 느낌을 냅니다.

scale(1.07, .94)어긋난 위상900ms
$duration: 900ms;
$easing: ease-in-out;
$color: $subject-cream;

.o-puru {
  animation: o-puru $duration $easing infinite;
}

@keyframes o-puru {
  0%, 100% { transform: scale(1, 1); }
  25%      { transform: scale(1.07, .94); }
  50%      { transform: scale(.95, 1.06); }
  75%      { transform: scale(1.03, .97); }
}

04살랑살랑 흔들리는(ゆらゆら)

.o-yura는 transform-origin을 위쪽에 고정하고 rotate만 좌우로 아주 천천히 오갑니다. 매달린 세일 태그나 알림 종 장식이 바람에 살랑이듯 보이게 할 때 씁니다.

rotatetransform-origin: top2.4s
$duration: 2.4s;
$easing: ease-in-out;
$color: $subject-blue;

.o-yura {
  animation: o-yura $duration $easing infinite;
  transform-origin: top center;
}

@keyframes o-yura {
  0%, 100% { transform: rotate(-6deg); }
  50%      { transform: rotate(6deg); }
}

05빙글빙글 도는(くるくる)

.o-kuru는 rotate 360도를 한 번에 linear로 밀지 않고 구간마다 통과 지점을 다르게 둬 한 박자 튕기는 듯한 리듬을 만듭니다. 새로고침이나 동기화 아이콘에 씁니다.

rotate 360deg구간별 easing1.6s
$duration: 1.6s;
$easing: cubic-bezier(.65, 0, .35, 1);
$color: $subject-cream;

.o-kuru {
  animation: o-kuru $duration $easing infinite;
}

@keyframes o-kuru {
  0%   { transform: rotate(0deg); }
  55%  { transform: rotate(230deg); }
  80%  { transform: rotate(330deg); }
  100% { transform: rotate(360deg); }
}

06깡충깡충 뛰는(ぴょんぴょん)

.o-pyon은 착지 직전엔 scaleY를 눌러 납작하게, 뛰어오르는 순간엔 translateY와 scaleY를 늘려 통통 뛰는 리듬을 만듭니다. "더 보기" 화살표나 스크롤 유도 아이콘에 씁니다.

translateY+scaleY착지 스쿼시900ms
$duration: 900ms;
$easing: ease-in-out;
$color: $subject-blue;

.o-pyon {
  animation: o-pyon $duration $easing infinite;
}

@keyframes o-pyon {
  0%, 100% { transform: translateY(0)     scaleY(1); }
  10%      { transform: translateY(2px)   scaleY(.9); }
  40%      { transform: translateY(-16px) scaleY(1.08); }
  70%      { transform: translateY(2px)   scaleY(.9); }
  85%      { transform: translateY(0)     scaleY(1.03); }
}

07말랑말랑 휘는(ぐにゃぐにゃ)

.o-gunya는 border-radius 네 모서리 값을 각각 다른 타이밍으로 keyframes 안에서 바꿔 배경 블롭 도형이 말랑하게 형태를 바꾸며 떠다니게 합니다. 히어로 배경이나 기능 카드 뒤 악센트 도형에 씁니다.

border-radius 4분할비대칭 keyframes3.2s
$duration: 3.2s;
$easing: ease-in-out;
$color: $subject-cream;

.o-gunya {
  animation: o-gunya $duration $easing infinite;
}

@keyframes o-gunya {
  0%, 100% { border-radius: 42% 58% 60% 40% / 46% 40% 60% 54%; transform: rotate(0deg); }
  33%      { border-radius: 62% 38% 45% 55% / 38% 56% 44% 62%; transform: rotate(6deg); }
  66%      { border-radius: 48% 52% 65% 35% / 60% 42% 58% 40%; transform: rotate(-5deg); }
}

08반짝(キラッ)

.o-kira는 ::before·::after로 만든 작은 별 두 개에 서로 다른 delay를 줘 scale·opacity로 번갈아 반짝이게 합니다. 추천 배지나 신규 기능 표시가 주기적으로 반짝이며 눈길을 끌게 합니다.

scale+opacity::before/::after1.8s
$duration: 1.8s;
$easing: ease-in-out;
$color: $subject-blue;

.badge.o-kira::before { animation: o-kira $duration $easing infinite; }
.badge.o-kira::after  { animation: o-kira $duration $easing infinite; animation-delay: .4s; }

@keyframes o-kira {
  0%, 45%, 100% { opacity: 0; transform: scale(.4); }
  62%           { opacity: 1; transform: scale(1.3); }
  78%           { opacity: 0; transform: scale(.6); }
}

09스르륵 매끄러운(ぬるっと)

.o-nuru는 마우스를 올리거나 포커스하면 화살표가 ease-out-expo에 가까운 cubic-bezier로 미끄러지듯 이동합니다. 다른 여덟 개의 탄성과 대비되는 매끈한 느낌이라 "자세히 보기" 링크에 어울립니다.

translateXcubic-bezier(.16, 1, .3, 1)700ms
$duration: 700ms;
$easing: cubic-bezier(.16, 1, .3, 1);
$color: $subject-blue;

.link.o-nuru:hover .link__arrow,
.link.o-nuru:focus-visible .link__arrow { animation: o-nuru $duration $easing 1; }

@keyframes o-nuru {
  0%   { transform: translateX(0)    scale(1); }
  100% { transform: translateX(28px) scale(1.15); }
}

어디서 깨지는가 — is-demo 클래스를 안 지우면 생기는 함정

02와 09처럼 호버로 트리거되는 항목은 실제 동작용 규칙(:hover·:focus-visible)과 이 페이지를 자동 재생시키는 .is-demo 루프용 규칙을 같은 요소에 함께 둡니다. 두 규칙은 특정성이 똑같아서(클래스 두세 개, 동점) 나중에 선언된 쪽이 이깁니다. 그래서 .is-demo를 진짜 화면에 붙인 채로 배포하면, 사용자가 마우스를 올려도 짧은 트리거 애니메이션 대신 2초짜리 데모 루프가 재생됩니다. zip을 코드에 붙여 쓸 때는 is-demo 클래스만 지우면 되고, 반대로 is-demo를 남겨 두고 싶다면 실사용 규칙을 스타일시트 더 아래에 적어야 합니다. 아홉 개 중 07 말랑말랑 휘는 항목의 평균 강도(강도_평균)는 41.6으로 가장 낮게 측정됐는데, border-radius만으로 만든 곡선이 배경과의 대비가 완만해서입니다. 대비가 약한 배경 위에 올리면 거의 안 보일 수 있으니 배경과 색 차이가 뚜렷한 곳에 둬야 합니다. duration·easing·색을 전부 맞춰 둔 vanilla·React 파일 아홉 벌을 묶은 zip은 압축 비밀번호 kxj6u9k6로 열립니다.

접근성

운영체제에 동작 줄이기 설정이 켜져 있다면 아홉 개 모두 @media (prefers-reduced-motion: reduce) 블록이 animation: none으로 움직임을 지웁니다. 01 둥실둥실·04 살랑살랑·07 말랑말랑처럼 배경을 꾸미기만 하는 동작은 지워도 잃는 정보가 없고, 02 포용·09 스르륵은 애니메이션이 빠져도 버튼 클릭이나 링크 이동 같은 실제 기능은 그대로 살아 있습니다. 이 미디어 쿼리를 지원하는 브라우저 목록은 MDN의 prefers-reduced-motion 문서 아래쪽 호환성 표에서 확인할 수 있습니다.

@media (prefers-reduced-motion: reduce) {
  .o-float, .o-puru, .o-yura,
  .o-kuru, .o-pyon, .o-gunya { animation: none; }
  .badge.o-kira::before, .badge.o-kira::after { animation: none; }
  .btn.o-poyon:hover, .btn.o-poyon:focus-visible,
  .link.o-nuru:hover .link__arrow,
  .link.o-nuru:focus-visible .link__arrow { animation: none; }
}

이 시리즈의 다른 묶음은 CSS 애니메이션 카테고리에서 모아 보고, 이 사이트를 처음 본다면 소개 페이지부터 훑어보십시오.

FAQ

아홉 개를 한 화면에 다 써도 됩니까?

기술적으로는 가능하지만 한 화면에 두세 개까지만 권합니다. 02 포용과 08 반짝은 똑같이 "여기를 봐 달라"고 조르는 역할이라 한 화면에 같이 두면 시선이 갈라집니다. 반대로 01 둥실둥실처럼 조용한 것과 05 빙글빙글처럼 상태를 알리는 것은 역할이 겹치지 않아 같이 둬도 됩니다.

왜 데모 화면에서는 02와 09가 계속 움직입니까?

실제 화면에서 02와 09는 마우스를 올리거나 포커스할 때만 움직이는 호버 트리거입니다. 이 페이지의 데모는 사람이 손을 대지 않아도 움직임을 보여줘야 해서 .is-demo 클래스로 2초마다 스스로 재생되게 강제한 것이고, 실제 코드에 붙일 때는 이 클래스를 지우면 됩니다.

React에서는 호버 트리거를 어떻게 재현합니까?

zip의 react/ 폴더는 duration·easing·color를 props로 받아 CSS 변수로 넘기고, 02·09처럼 호버로 트리거되는 항목도 상태 없이 CSS 모듈의 :hover·:focus-visible 선택자로 그대로 처리합니다. 01·03~08처럼 상시 반복되는 항목은 컴포넌트가 마운트되는 순간부터 애니메이션이 돕니다.

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

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