나는 이제 퍼블리셔가 사용할 수 있는 모든 컴포넌트를 제작하기로 마음 먹었다. 진짜다. 진짜... 할거다~ 이것은 버튼 시리즈다.
일단 처음 작업은 가장 친숙한 HTML과 CSS, JS로 시작하겠다.
CodePen용 코드. 버튼 목록이라
<ul>+<li>로 구성하고, 4열로 정렬했다.
접근성 요소(포커스 표시, 색 비의존 라벨, 모션 최소화) 포함.
<main class="wrap">
<h1 class="sr-only">호버 버튼 효과 갤러리</h1>
<ul class="btn-list">
<li>
<button class="btn b1">Hover</button>
<span class="label">색 + 확대 <em>fill scale</em></span>
</li>
<li>
<button class="btn b2">Hover</button>
<span class="label">색 반전 <em>outline</em></span>
</li>
<li>
<button class="btn b3">Hover</button>
<span class="label">떠오름 <em>lift</em></span>
</li>
<li>
<button class="btn b4"><span class="txt">Hover</span></button>
<span class="label">슬라이드 채움 <em>slide</em></span>
</li>
<li>
<button class="btn b5"><span class="txt">Hover</span></button>
<span class="label">물결 <em>ripple</em></span>
</li>
<li>
<button class="btn b6">Hover</button>
<span class="label">글로우 <em>glow</em></span>
</li>
<li>
<button class="btn b7">Hover</button>
<span class="label">그라데이션 <em>gradient</em></span>
</li>
<li>
<button class="btn b8">Hover</button>
<span class="label">자간 확장 <em>spacing</em></span>
</li>
<li>
<button class="btn b9">Hover</button>
<span class="label">눌림 <em>press</em></span>
</li>
<li>
<button class="btn b10">Hover</button>
<span class="label">모서리 변형 <em>radius</em></span>
</li>
<li>
<button class="btn b11">Hover <span class="arrow" aria-hidden="true">→</span></button>
<span class="label">아이콘 이동 <em>icon shift</em></span>
</li>
<li>
<button class="btn b12" id="magnetic">Hover</button>
<span class="label">자석 <em>magnetic</em></span>
</li>
</ul>
</main>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #fff3ed;
font-family: system-ui, sans-serif;
}
/* 스크린리더 전용 텍스트 */
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.wrap { max-width: 720px; padding: 32px; }
.btn-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(4, 1fr); /* 4열 고정 */
gap: 28px 18px;
}
/* 모바일: 2열 */
@media (max-width: 600px) {
.btn-list { grid-template-columns: repeat(2, 1fr); }
}
.btn-list li {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.label {
font-size: 13px;
font-weight: 600;
color: #4a3f6b;
text-align: center;
line-height: 1.4;
}
.label em {
display: block;
font-size: 11px;
font-weight: 400;
font-style: normal;
color: #8a7fb0;
}
.btn {
padding: 14px 26px;
font-size: 15px;
font-weight: 600;
border: none;
border-radius: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
font-family: inherit;
}
.btn:focus-visible {
outline: 3px solid #2d3436;
outline-offset: 3px;
}
/* ① 색 + 확대 */
.b1 { background: #6c5ce7; color: #fff; transition: all .25s ease; }
.b1:hover { background: #4834b4; transform: scale(1.06); }
/* ② 색 반전 */
.b2 { background: #fff; color: #4834b4; border: 2px solid #4834b4; transition: all .25s ease; }
.b2:hover { background: #4834b4; color: #fff; }
/* ③ 떠오름 */
.b3 { background: #fff; color: #333; box-shadow: 0 2px 6px rgba(0,0,0,.12); transition: all .25s ease; }
.b3:hover { transform: translateY(-4px); box-shadow: 0 10px 22px rgba(0,0,0,.18); }
/* ④ 슬라이드 채움 */
.b4 { background: #008068; color: #fff; transition: all .3s ease; }
.b4::before { content: ""; position: absolute; inset: 0; background: #005c4a; transform: translateX(-100%); transition: transform .3s ease; z-index: 0; }
.b4:hover::before { transform: translateX(0); }
.b4 .txt { position: relative; z-index: 1; }
/* ⑤ 물결 */
.b5 { background: #c0492a; color: #fff; transition: color .3s; }
.b5::before { content: ""; position: absolute; left: 50%; top: 50%; width: 0; height: 0; background: #8a2f19; border-radius: 50%; transform: translate(-50%,-50%); transition: width .4s ease, height .4s ease; z-index: 0; }
.b5:hover::before { width: 250%; height: 250%; }
.b5 .txt { position: relative; z-index: 1; }
/* ⑥ 글로우 */
.b6 { background: #0069b3; color: #fff; transition: box-shadow .3s ease; }
.b6:hover { box-shadow: 0 0 18px 2px #4da3e0; }
/* ⑦ 그라데이션 흐름 */
.b7 { color: #fff; background: linear-gradient(90deg, #6c5ce7, #d6467f, #6c5ce7); background-size: 200% 100%; background-position: 0 0; transition: background-position .5s ease; }
.b7:hover { background-position: 100% 0; }
/* ⑧ 자간 확장 */
.b8 { background: #2d3436; color: #fff; transition: letter-spacing .25s ease, background .25s; }
.b8:hover { letter-spacing: 3px; background: #6c5ce7; }
/* ⑨ 눌림 */
.b9 { background: #008068; color: #fff; transition: transform .2s; }
.b9:hover { transform: translateY(-2px); }
.b9:active { transform: scale(.92); }
/* ⑩ 모서리 변형 */
.b10 { background: #fff; color: #b02a6b; border: 2px solid #b02a6b; border-radius: 30px; transition: all .3s ease; }
.b10:hover { border-radius: 10px; background: #b02a6b; color: #fff; }
/* ⑪ 아이콘 이동 */
.b11 { background: #9a6a08; color: #fff; }
.b11 .arrow { display: inline-block; transition: transform .3s ease; }
.b11:hover .arrow { transform: translateX(5px); }
/* ⑫ 자석 */
.b12 { background: #555e62; color: #fff; transition: transform .15s ease; }
/* 모션 최소화 배려 */
@media (prefers-reduced-motion: reduce) {
.btn, .btn::before, .b11 .arrow { transition: none !important; }
}
const magnetic = document.getElementById("magnetic");
magnetic.addEventListener("mousemove", (e) => {
const rect = magnetic.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
magnetic.style.transform = `translate(${x * 0.4}px, ${y * 0.4}px)`;
});
magnetic.addEventListener("mouseleave", () => {
magnetic.style.transform = "translate(0, 0)";
});
| # | 이름 | 핵심 기술 |
|---|---|---|
| 1 | 색 + 확대 | transition + transform: scale() |
| 2 | 색 반전 | :hover로 배경/글자색 교체 |
| 3 | 떠오름 | translateY + box-shadow |
| 4 | 슬라이드 채움 | ::before + translateX |
| 5 | 물결 | ::before 원형 확대 |
| 6 | 글로우 | box-shadow 번짐 |
| 7 | 그라데이션 흐름 | background-position 이동 |
| 8 | 자간 확장 | letter-spacing 전환 |
| 9 | 눌림 | :active + scale |
| 10 | 모서리 변형 | border-radius 전환 |
| 11 | 아이콘 이동 | 자식만 translateX |
| 12 | 자석 | JS로 마우스 위치 추적 |
.25s → .6s로 느린 느낌 체감ease → cubic-bezier(.68,-0.55,.27,1.55) 넣으면 통통 튀는 느낌0.4를 0.8로 올리면 더 강하게 끌려옴overflow: hidden으로 잘라내는 패턴 — 응용 범위가 넓다::before가 글자를 덮어서 안 보임 → .txt에 position: relative; z-index: 1로 해결overflow: hidden + ::before 조합이 슬라이드·물결 효과의 핵심