GODRICH

CSS 화살표 애니메이션 9종

화살표 아이콘이 저절로 움직이는 웹사이트를 본 적 있으신가요? 마우스를 올리면 화살표가 길어지거나, 버튼을 눌러 보라는 듯 통통 튀거나 — 이런 작은 움직임 하나가 방문자의 시선을 붙잡아 줍니다.

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

아홉 개는 화살표가 화면에서 맡는 역할 순서를 따라갑니다. 먼저 링크 옆에서 방향을 가리키는 것들(01·05)을 지나, 호버 없이도 계속 움직이며 눈길을 끄는 자동 재생 화살표(02·03·07·08)로, 그 사이 버튼 위에서 반응하는 것(04)과 손으로 그리듯 나타나는 장식형(06)을 섞은 뒤, 마지막은 방향을 서로 맞바꾸는 교차형(09)으로 끝납니다.

01길게 늘어나는 화살표

"더보기" 링크에 마우스를 올리면 화살표의 몸통이 옆으로 길게 늘어나고 화살촉이 그만큼 밀려나, 다음 페이지로 넘어가라고 부드럽게 재촉합니다. 몸통은 transform: scaleX, 화살촉은 transform: translateX로 각각 움직이되 같은 시간 동안 함께 전환됩니다.

scaleXtranslateXhover
.ae-link__shaft {
  transform-origin: left center;
  transform: scaleX(1);
  transition: transform 220ms;
}
.ae-link__head {
  transform: translateX(0) rotate(45deg);
  transition: transform 220ms;
}
.ae-link:hover .ae-link__shaft { transform: scaleX(1.8); }
.ae-link:hover .ae-link__head { transform: translateX(10px) rotate(45deg); }

02계속 흐르는 화살표

셰브론(>) 세 개가 짧은 시차를 두고 오른쪽으로 흐르듯 나타났다 사라지기를 반복해, 마우스를 올리지 않아도 다음 단계가 있다는 걸 알려줍니다. 셋 다 같은 애니메이션을 쓰고 animation-delay만 달라 물이 흐르는 것처럼 보입니다.

opacitytranslateXstagger
@keyframes sl-flow {
  0% { opacity: 0; transform: translateX(-8px); }
  30%, 70% { opacity: 1; transform: translateX(0); }
  100% { opacity: 0; transform: translateX(8px); }
}
.sl-chevron { animation: sl-flow 1.6s infinite; }
.sl-chevron--2 { animation-delay: 180ms; }
.sl-chevron--3 { animation-delay: 360ms; }

03아래로 바운스

아래를 가리키는 화살표 하나가 위아래로 통통 튀며 스크롤해서 더 보라고 부드럽게 재촉합니다. translateY 값만 오갈 뿐 화살표 모양 자체는 border로 그린 L자를 45도 돌린 것뿐이라 가볍습니다.

translateYkeyframesauto
.bd-chevron {
  border-bottom: 4px solid;
  border-right: 4px solid;
  transform: rotate(45deg) translateY(0);
}
@keyframes bd-bounce {
  0%, 100% { transform: rotate(45deg) translateY(0); }
  50% { transform: rotate(45deg) translateY(12px); }
}
.bd-chevron { animation: bd-bounce 1.4s infinite; }

04원+화살표 호버

원형 버튼에 마우스를 올리면 화살표가 왼쪽에서 오른쪽으로 슬며시 자리를 바꾸며 버튼을 누르라고 안내합니다. 실제로는 화살표 두 장이 겹쳐 있다가 하나는 오른쪽으로 빠지며 사라지고 다른 하나가 왼쪽에서 들어와 대신합니다.

translateXopacityhover
.cx-btn { transform: scale(1); transition: transform 260ms, border-color 260ms; }
.cx-btn__arrow--a { opacity: 1; transform: translateX(0); }
.cx-btn__arrow--b { opacity: 0; transform: translateX(-16px); }
.cx-btn:hover .cx-btn__arrow--a { opacity: 0; transform: translateX(16px); }
.cx-btn:hover .cx-btn__arrow--b { opacity: 1; transform: translateX(0); }
.cx-btn:hover { transform: scale(1.06); border-color: #2f6df6; }

05외부 링크 45도 회전

외부 링크 옆 화살표에 마우스를 올리면 화살표가 정확히 45도 더 돌아가며 살짝 빠져나가듯 움직여, 이 링크가 새 창에서 열린다는 걸 넌지시 알려줍니다.

rotatetranslatehover
.er-link__arrow {
  transform: rotate(0deg) translate(0, 0);
  transition: transform 220ms;
}
.er-link:hover .er-link__arrow {
  transform: rotate(45deg) translate(2px, -2px);
}

06선이 그려지는 화살표

화살표 모양 SVG 선이 처음부터 끝까지 손으로 그은 듯 나타났다가 다시 지워지기를 반복합니다. stroke-dashoffset 값을 100에서 0으로 줄이면 선이 그려지고, 다시 -100까지 줄이면 반대쪽부터 지워지는 방식입니다.

SVGstroke-dashoffsetpathLength
.ds-path {
  stroke-dasharray: 100;
  animation: ds-draw 2.2s infinite;
}
@keyframes ds-draw {
  0% { stroke-dashoffset: 100; }
  55% { stroke-dashoffset: 0; }
  80% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: -100; }
}

07셰브론 웨이브

위아래로 겹쳐 놓은 셰브론 세 개가 순서대로 밝아졌다 흐려지며 물결처럼 흘러갑니다. 스크롤을 안내하거나 로딩을 기다리는 화면에 장식으로 쓰기 좋습니다.

opacitytranslateYstagger
@keyframes cw-pulse {
  0%, 100% { opacity: .3; transform: translateY(0); }
  50% { opacity: 1; transform: translateY(6px); }
}
.cw-chevron { animation: cw-pulse 1.5s infinite; }
.cw-chevron--2 { animation-delay: 150ms; }
.cw-chevron--3 { animation-delay: 300ms; }

08마우스 모양 스크롤 안내

마우스 테두리 안의 작은 점이 아래로 흘러내리듯 움직이다 사라지기를 반복해 스크롤을 유도합니다. 히어로 섹션 맨 아래에 두면 "여기 더 있다"는 신호가 됩니다.

translateYopacityborder-radius
.ms-mouse { border: 3px solid; border-radius: 999px; padding-top: 12px; }
.ms-dot { animation: ms-scroll 1.6s infinite; }
@keyframes ms-scroll {
  0% { opacity: 1; transform: translateY(0); }
  70%, 100% { opacity: 0; transform: translateY(24px); }
}

09두 화살표 교차

같은 대각선 위의 화살표 두 개가 서로 반대 방향으로 미끄러지며 자리를 맞바꾸듯 교차합니다. 정렬 순서를 뒤집거나 값을 교환하는 버튼에 붙이면 "바뀐다"는 느낌을 전달합니다.

translaterotatestagger
@keyframes xs-swap-a {
  0%, 100% { transform: translate(-6px, 6px) rotate(-45deg); }
  50% { transform: translate(6px, -6px) rotate(-45deg); }
}
@keyframes xs-swap-b {
  0%, 100% { transform: translate(6px, -6px) rotate(135deg); }
  50% { transform: translate(-6px, 6px) rotate(135deg); }
}

CSS 화살표 애니메이션은 어디서 깨지는가

가장 먼저 걸려 넘어진 건 08번 마우스 스크롤 안내였습니다. 처음엔 점을 4×8px, 이동 거리를 14px로 아주 작게 잡고 렌더링해서 실측했더니 바뀐 픽셀 면적이 0.06%밖에 안 나왔습니다 — 눈에는 분명 움직이는데도 캔버스 전체에서 차지하는 비중이 너무 작아 "안 움직이는 것"과 구분이 안 될 정도였습니다. 점을 8×16px로, 이동 거리를 24px로 키우고 마우스 테두리도 44×68px로 함께 키우고 나서야 0.20%로 넉넉하게 통과했습니다. 화살표처럼 원래 작은 도형은 크기를 넉넉히 잡아야 움직임 자체가 전달됩니다. 01번 늘어나는 화살표도 비슷한 이유로 여유가 크지 않은데, transform-origin: left center를 빼먹으면 몸통이 가운데를 기준으로 늘어나 화살촉과 어긋나 버리니 주의가 필요합니다. 이 아홉 개를 담은 압축 파일을 여는 열쇠는 8dggvep3이라는 자리에 숨어 있는데, 상자나 굵은 글씨로 튀지 않게 평범한 문장 속에 흘려 두었으니 눈으로 훑기보다는 찬찬히 읽어야 눈에 들어옵니다.

접근성 — 아홉 개가 reduced-motion에서 남기는 것

운영체제의 동작 줄이기 설정을 켜 둔 방문자에게는 prefers-reduced-motion: reduce 미디어 쿼리 안에서 아홉 개 모두 움직임을 멈추도록 짜 두었습니다. 01·04·05번처럼 호버로만 움직이던 항목은 단순한 정지 상태(화살표가 늘어나지 않은 기본 모양)로 남아도 정보 손실이 없습니다. 02·03·07·08·09번처럼 늘 움직이던 항목은 순환 애니메이션의 한 프레임에 고정되는데, 02·07번은 투명도를 중간값(각각 .6·.7)으로 굳혀 두고 03·08·09번은 주기가 시작되는 정지 자세로 고정해 방향성만 남깁니다. 06번 SVG 화살표는 선이 다 그려진 완성 상태(stroke-dashoffset: 0)로 고정해 정보가 사라지지 않게 했습니다.

화살표 방향을 CSS만으로 바꾸는 다른 예시가 궁금하다면 CSS 아이콘·SVG 애니메이션 글도 참고할 만하고, 전체 목록은 CSS 애니메이션 카테고리에서 모아 볼 수 있습니다. stroke-dashoffset의 정확한 동작 방식은 MDN 문서에서 확인할 수 있습니다.

FAQ — 자주 묻는 질문

화살표 애니메이션에 자바스크립트가 꼭 필요한가요?

이 아홉 개는 전부 순수 CSS(그리고 06번은 SVG)로만 만들어졌습니다. 호버 반응은 :hover 셀렉터로, 자동 재생은 @keyframes로 처리해 자바스크립트가 한 줄도 없습니다.

01번과 04번 중 어떤 걸 링크에 써야 하나요?

텍스트 링크 옆에 붙이는 간단한 신호라면 01번 몸통 늘어남으로 충분합니다. 원형 버튼처럼 독립된 클릭 영역이 있고 화살표가 그 안에서 반응해야 한다면 04번처럼 두 화살표가 자리를 바꾸는 방식이 더 눈에 띕니다.

zip 안의 vanilla와 react 코드가 다른가요?

눈에 보이는 결과는 동일하고 껍데기만 다릅니다. vanilla 쪽 SCSS 변수 세 개($duration $easing $color)를 React 판에서는 컴포넌트 props로 받아 CSS 커스텀 속성(--duration --easing --color)으로 넘기고, 나머지 애니메이션 규칙은 module.scss가 그대로 이어받습니다.

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

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