07화살표 밀기
호버에 화살표 선이 늘어나며 오른쪽으로 민다.
쓰임다음·더 보기·계속
8px멀리 14px선 없이
<main class="stage">
<button class="btn is-demo" type="button">다음<span class="arrow" aria-hidden="true"><i class="arrow__line"></i><i class="arrow__head"></i></span></button>
</main>// ── 변수 3개 ──────────────────────────────────────────
$push: 8px; // 화살표가 얼마나 밀리나
$duration: 240ms;
$color: #17141a;
html, body { margin: 0; overflow: hidden; } // iframe 안에서 스크롤바가 생기지 않게 (실측 2026-09-09: body 여백 8px 이 100vh 를 넘겼다)
.stage { min-height: 100vh; display: grid; place-items: center; margin: 0; background: #17141a; font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif; }
.btn {
display: inline-flex;
align-items: center;
gap: 14px;
border: 0;
border-radius: 14px;
padding: 20px 26px 20px 40px;
font-weight: 800;
font-size: 20px;
line-height: 1;
font-family: inherit;
color: $color;
background: #fff7e6;
cursor: pointer;
}
// 화살표 = 선 + 머리. 선은 늘어나고(scaleX) 머리는 밀린다(translateX)
.arrow { position: relative; width: 26px; height: 16px; display: block; }
.arrow__line, .arrow__head { position: absolute; display: block; background: currentColor; }
.arrow__line { left: 0; top: 7px; width: 100%; height: 2.5px; transform-origin: left; transform: scaleX(.55); transition: transform $duration cubic-bezier(.2, .8, .2, 1); } // 규칙8: 화살표 선 왼쪽으로 늘어남
.arrow__head { right: 0; top: 2px; width: 12px; height: 12px; background: transparent; border-top: 2.5px solid currentColor; border-right: 2.5px solid currentColor; transform: rotate(45deg); transition: transform $duration cubic-bezier(.2, .8, .2, 1); }
.btn:hover .arrow__line, .btn:focus-visible .arrow__line { transform: scaleX(1); }
.btn:hover .arrow__head, .btn:focus-visible .arrow__head { transform: rotate(45deg) translate($push * .7, -$push * .7); }
.btn.is-demo .arrow__line { animation: line-loop 2s cubic-bezier(.2, .8, .2, 1) infinite; }
.btn.is-demo .arrow__head { animation: head-loop 2s cubic-bezier(.2, .8, .2, 1) infinite; }
@keyframes line-loop { 0%, 20% { transform: scaleX(.55); } 45%, 70% { transform: scaleX(1); } 100% { transform: scaleX(.55); } }
@keyframes head-loop { 0%, 20% { transform: rotate(45deg); } 45%, 70% { transform: rotate(45deg) translate($push * .7, -$push * .7); } 100% { transform: rotate(45deg); } }
@media (prefers-reduced-motion: reduce) {
.arrow__line, .arrow__head { transition: none; animation: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.btn {
display: inline-flex;
align-items: center;
gap: 14px;
border: 0;
border-radius: 14px;
padding: 20px 26px 20px 40px;
font-weight: 800;
font-size: 20px;
line-height: 1;
font-family: inherit;
color: #17141a;
background: #fff7e6;
cursor: pointer;
}
.arrow {
position: relative;
width: 26px;
height: 16px;
display: block;
}
.arrow__line, .arrow__head {
position: absolute;
display: block;
background: currentColor;
}
.arrow__line {
left: 0;
top: 7px;
width: 100%;
height: 2.5px;
transform-origin: left;
transform: scaleX(0.55);
transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.arrow__head {
right: 0;
top: 2px;
width: 12px;
height: 12px;
background: transparent;
border-top: 2.5px solid currentColor;
border-right: 2.5px solid currentColor;
transform: rotate(45deg);
transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.btn:hover .arrow__line, .btn:focus-visible .arrow__line {
transform: scaleX(1);
}
.btn:hover .arrow__head, .btn:focus-visible .arrow__head {
transform: rotate(45deg) translate(5.6px, -5.6px);
}
.btn.is-demo .arrow__line {
animation: line-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.btn.is-demo .arrow__head {
animation: head-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes line-loop {
0%, 20% {
transform: scaleX(0.55);
}
45%, 70% {
transform: scaleX(1);
}
100% {
transform: scaleX(0.55);
}
}
@keyframes head-loop {
0%, 20% {
transform: rotate(45deg);
}
45%, 70% {
transform: rotate(45deg) translate(5.6px, -5.6px);
}
100% {
transform: rotate(45deg);
}
}
@media (prefers-reduced-motion: reduce) {
.arrow__line, .arrow__head {
transition: none;
animation: none !important;
}
}document.querySelectorAll('.btn').forEach(function (b) { b.addEventListener('pointerenter', function () { b.classList.remove('is-demo'); }); });import type { CSSProperties, ReactNode } from "react";
import styles from "./ArrowButton.module.scss";
export type ArrowButtonProps = { children: ReactNode; push?: number; duration?: number; onClick?: () => void };
export default function ArrowButton({ children, push = 8, duration = 240, onClick }: ArrowButtonProps) {
const vars = { "--push": `${push}px`, "--duration": `${duration}ms` } as CSSProperties;
return (
<button type="button" className={styles.btn} style={vars} onClick={onClick}>
{children}
<span className={styles.arrow} aria-hidden="true"><i className={styles.line} /><i className={styles.head} /></span>
</button>
);
}07 화살표 밀기 — 측정 누적면적 0.186% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · CSS 버튼 애니메이션 9종 — 복붙하면 되는 SCSS + React
이 데모가 속한 글
CSS 버튼 애니메이션 9종 — 복붙하면 되는 SCSS + ReactCSS 버튼 애니메이션 아홉 개를 한 번에 모았습니다. 호버, 클릭, 로딩, 실패, 확인까지 버튼 하나가 겪는 상황을 아홉 장면으로 나눠 하나씩 담았고, 전부 transform과 opacity만 움직입니다. 격자에서 이 글의 데모 전부 보기
