GODRICH

9 css button design ideas — dán một file là chạy

css button design là quyết định màu, hình dáng nút và cách nó phản ứng với chuột, chỉ bằng vài dòng code.

Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip

Chín kiểu không xếp theo độ nổi tiếng mà theo vòng đời thật của một nút trên màn hình. Đầu tiên là nhóm bắt mắt — gradient kẹo (01), vệt sáng (02), blob mềm (03). Tiếp theo là nhóm trả lời khi hover — viền tự vẽ (04), mũi tên trượt (05), quét hai màu (06). Rồi đến khoảnh khắc nhấn — vòng gợn (07), nút 3D (08) — và cuối cùng là chờ đợi sau cú nhấn, pill loader (09).

01Nút gradient kẹo chảy

Một tấm vải màu kẹo rộng gấp ba nút được trải bên dưới và mỗi nhịp bị đẩy sang trái đúng một chu kỳ sọc. Thứ duy nhất di chuyển là một translateX nên không bao giờ tính lại bố cục, và vì chu kỳ đúng 240px nên bạn không bao giờ thấy điểm nối của hoa văn. Hợp với nút đăng ký ở màn hình đầu của landing.

repeating-linear-gradienttranslateX(-240px) 한 주기overflow: hidden
.btn__bg {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 720px;
  background: repeating-linear-gradient(to right,
    $color 0 58px, $candy-b 64px 118px, $candy-c 124px 178px, $color 184px 240px);
}
.stage.is-demo .btn__bg { animation: gradient-flow $duration $easing infinite; }
@keyframes gradient-flow {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-240px); }
}

02Nút quét vệt sáng

Một dải sáng trắng nghiêng lướt qua nút tối một lần rồi để lại vệt long lanh như ánh đèn lướt trên thủy tinh. Dải sáng là phần tử giả ::after, đặt nghiêng bằng skewX(-20deg) và chỉ di chuyển bằng translateX nên nút rộng hay hẹp vẫn chạy đúng. Dành cho nút thanh toán, nút mua xứng đáng được nhìn thêm một lần.

translateX(±150%)skewX(-20deg)overflow: hidden
.btn::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: -60%;
  width: 60%;
  background: linear-gradient(115deg,
    transparent 0%, rgba(255, 255, 255, .85) 50%, transparent 100%);
  transform: skewX(-20deg) translateX(-10%);
}
@keyframes sweep {
  0%, 10% { transform: skewX(-20deg) translateX(-10%); }
  55%     { transform: skewX(-20deg) translateX(310%); }
  100%    { transform: skewX(-20deg) translateX(310%); }
}

03Nút blob mềm

Hai hình tròn bạc hà nấp sau nhãn phồng ra hai bên làm khung nút giãn ra thụt vào như thạch. Hai hình tròn cùng đúng màu nút nên mép của chúng hòa làm một, và độ phồng chỉ dùng một scale kèm chút dịch ngang. Rất hợp với nút thêm vào giỏ ở shop đồ trẻ em.

::before·::after scaleborder-radius: 50%$ease-pop
.btn::before, .btn::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: $color;
  transform: translateY(-50%) scale(.3);
}
.stage.is-demo .btn::before { animation: blob-l $duration $easing infinite; }
@keyframes blob-l {
  0%, 12% { transform: translateY(-50%) scale(.3); }
  40%     { transform: translateY(-50%) scale(1.3) translateX(-8px); }
  100%    { transform: translateY(-50%) scale(.3); }
}

04Nút viền tự vẽ

Viền nút tự vẽ mình vào từng nét như viết tay. Gắn thuộc tính pathLength="100" cho hình chữ nhật SVG thì dù độ dài thật là bao nhiêu cũng thành thước 100 vạch, nên chỉ cần hạ stroke-dashoffset từ 100 về 0 là xong bản vẽ. Sau đó một lớp nước màu nhạt đổ đầy, trọn một chu kỳ.

stroke-dashoffsetpathLength=100vector-effect: non-scaling-stroke
.btn__rect {
  fill: none;
  stroke: $color;
  stroke-width: 3;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}
.stage.is-demo .btn__rect { animation: draw $duration $easing infinite; }
@keyframes draw {
  0%, 12% { stroke-dashoffset: 100; }
  55%     { stroke-dashoffset: 0; }
  100%    { stroke-dashoffset: 100; }
}

05Nút trượt mũi tên

Hai ô nằm cạnh nhau — một ô chỉ có nhãn, một ô có nhãn kèm mũi tên — và cả dải trượt đúng nửa chiều rộng. Khi hover chữ bị đẩy sang trái còn mũi tên lướt vào chỗ của nó từ bên phải, và vì dải cố định độ rộng 200% nên nhãn dài ngắn thế nào cũng không lệch. Hợp với nút xem thêm dẫn sang nội dung kế tiếp.

격층 translateXoverflow: hidden$ease-spring
.btn__track { display: flex; width: 200%; height: 100%; }
.btn__cell  { flex: 0 0 50%; display: flex; align-items: center; }
.stage.is-demo .btn__track { animation: slide-in $duration $easing infinite; }
@keyframes slide-in {
  0%, 18%  { transform: translateX(0); }
  55%, 72% { transform: translateX(-50%); }
  88%      { transform: translateX(-4%); }
  100%     { transform: translateX(0); }
}

06Nút hai màu quét

Nút tách thành kem và đào, lớp màu dưới trải ra từ trái bằng scaleX làm cả mặt nút lật màu như nước loang. Chữ cũng có hai bản chồng nhau và đổi bằng opacity nên ngay khoảnh khắc chuyển màu vẫn đọc được. Hợp với nút bắt đầu trên thẻ giá.

scaleX 레이어 와이프transform-origin: leftcolor 전환
.btn__wash {
  position: absolute;
  inset: 0;
  background: $color;
  transform: scaleX(0);
  transform-origin: left;
}
@keyframes wash {
  0%, 12%  { transform: scaleX(0); }
  48%, 70% { transform: scaleX(1); }
  100%     { transform: scaleX(0); }
}
.stage.is-demo .btn__t-on-dark { animation: swap-in $duration $easing infinite; }

07Nút gợn vòng tròn

Đúng lúc nhấn, thay cho gợn sóng là một vòng viền loang ra ngoài như tiếng chuông. Vòng 3px nằm ở ::after, phóng to bằng scale trong khi opacity xóa dần, nên chỉ cần đổi một giá trị màu là đặt lên được mọi nút icon. Trái tim đồng thời phồng lên rồi co nhẹ, rất hợp nút like và bookmark.

::after 링 확산scale + opacity:active 트리거
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid $color;
  opacity: 0;
}
@keyframes ring {
  0%  { transform: scale(1);   opacity: .9; }
  34% { transform: scale(1.8); opacity: 0; }
  50% { transform: scale(1);   opacity: .9; }
  84% { transform: scale(1.8); opacity: 0; }
}

08Nút nhấn 3D

Một lớp cứng 8px nằm dưới nút khiến nó trông như phím bấm vật lý, và khi nhấn mặt nút lún đúng 8px bằng translateY trong khi lớp đó biến mất. Vòng lặp demo còn thêm cú nảy nhẹ khi thả tay; khi dùng thật :active chỉ cần hai dòng. Dành cho nút bắt đầu game và các nút to trong app trẻ em.

translateY 눌림box-shadow 두께:active 트리거
$depth: 8px;
.btn {
  box-shadow: 0 $depth 0 darken($color, 22%),
              0 #{$depth + 6px} #{$sp-3} rgba(23, 20, 26, .22);
}
.btn:active { transform: translateY($depth); }
@keyframes press {
  0%, 20%  { transform: translateY(0); }
  35%, 55% { transform: translateY($depth); }
  70%      { transform: translateY(-3px); }
  85%,100% { transform: translateY(0); }
}

09Nút pill có loader

Ngay lúc gửi file, nhãn mờ dần, nút khóa sang màu chàm đậm và trong chiếc pill một thanh ngắn trượt qua lại hai lần. Thanh chỉ di chuyển bằng translateX, hết thời gian chờ nhãn trở lại. Khi dùng thật chỉ cần bật tắt lớp is-busy trong lúc hàm gửi chạy.

라벨 opacity 스왑translateX 로더 막대$dur-loop 주기
.btn__bar {
  position: absolute;
  top: 0; bottom: 0;
  width: 60px;
  border-radius: $r-pill;
  background: $subject-cream;
  transform: translateX(-60px);
}
@keyframes bar-run {
  0%   { transform: translateX(-60px); }
  100% { transform: translateX(168px); }
}
@keyframes busy-bg {
  0%, 15%  { background: $color; }
  25%, 85% { background: $busy; }
  95%,100% { background: $color; }
}

Ở đâu nó dễ hỏng — pastel mềm máy không nhìn thấy

Mục 01 lúc đầu được làm ngược với những gì bạn đang thấy: bản thân nền gradient được dời bằng background-position, bảng màu là bộ pastel hòa mờ. Nhìn bằng mắt thì rõ ràng là đang chảy, nhưng đo khung hình bằng máy ra cường độ 0.0 và số khung di chuyển bằng 0. Giữa hai khung gradient tiến 10px, mà mức đổi màu trong quãng đó nằm dưới ngưỡng đo nên không pixel nào được tính là đã đổi. Vẽ lại bằng dải sọc có biên rõ và di chuyển cả lớp bằng translateX mới qua được phép đo. Khoảng chạy của vệt sáng ở mục 02 đặt gấp nhiều lần băng rộng cũng vì lý do đó. Mật khẩu giải nén của chín thư mục là 3vxxw8yh, gõ đúng như vậy thì mới mở được. Chu kỳ sọc của gradient (240px) phải khớp chính xác với khoảng di chuyển cũng là điều mục này chứng minh ngay trên màn hình — lệch đúng 4px là hoa văn giật một lần mỗi vòng.

Khả năng tiếp cận

Cả chín kiểu đều rơi về animation: none dưới prefers-reduced-motion: reduce, và mỗi kiểu có một khung hình tĩnh vẫn tự giải thích được mình là gì. Mục 01 dừng ở vị trí thấy được dải sọc, mục 04 dừng với viền vẽ xong hoàn chỉnh, mục 05 dừng ở ô có cả nhãn và mũi tên. Thanh của mục 09 đỗ giữa đường để màu và hình vẫn nói "đang tải". Mọi nút hiện viền :focus-visible khi dùng bàn phím. Cách bật/tắt cài đặt này được ghi trong trang prefers-reduced-motion của MDN.

FAQ

Không biết JavaScript có dùng được không?

Có, cả chín kiểu chạy thuần bằng CSS (SCSS), không một dòng JavaScript. Ngay mục 09 khi dùng thật cũng chỉ cần thêm bớt lớp is-busy trên nút. Nếu muốn thêm các bước trước đó như ripple và hover, bài 9 hiệu ứng nút CSS có sẵn.

Đổi màu theo thương hiệu thì sửa ở đâu?

Biến $color trên đầu SCSS của mỗi mục là màu gốc. Mục 01 có thêm hai màu phụ ngoài $color, còn mục 08 tự sinh cạnh đậm bằng darken($color, 22%). Ở mục 06 $color là màu đào lớp dưới, nên muốn đổi màu trên thì sửa luôn dòng nền.

Trong zip có gì?

Mỗi mục gồm index.html, style.scss, style.css đã biên dịch, và cặp .tsx + .module.scss đặt thẳng vào dự án React. Duration, easing, color được kéo ra thành props nên đổi một giá trị là thành biến thể. Vật liệu nút khác tìm được qua tìm kiếm trên site.

Nhập mật khẩu file nén

Mật khẩu nằm ngay trong bài viết này. Đọc tới đó bạn sẽ thấy.