GODRICH
#css-animation#button#loader#transition#scroll#ui#xu-huong-thiet-ke

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

Bạn thấy gì

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.

Nút gradient kẹo chảy
Mở trong tab mới

01 · Nút gradient kẹo chảy — Diện tích đo được 9.899% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

repeating-linear-gradienttranslateX(-240px) 한 주기overflow: hidden

Dùng ở đâu

Hợp với nút đăng ký ở màn hình đầu của landing.

.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

Chuyện gì diễn ra trên màn hình

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.

Nút quét vệt sáng
Mở trong tab mới

02 · Nút quét vệt sáng — Diện tích đo được 10.349% · khung hình chuyển động 9/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

translateX(±150%)skewX(-20deg)overflow: hidden

Chỗ dùng

Dành cho nút thanh toán, nút mua xứng đáng được nhìn thêm một lần.

.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

Chuyển động thế nào

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.

Nút blob mềm
Mở trong tab mới

03 · Nút blob mềm — Diện tích đo được 3.052% · khung hình chuyển động 14/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

::before·::after scaleborder-radius: 50%$ease-pop

Dùng khi nào

Rất hợp với nút thêm vào giỏ ở shop đồ trẻ em.

.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ẽ

Bạn thấy gì

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ẽ.

Nút viền tự vẽ
Mở trong tab mới

04 · Nút viền tự vẽ — Diện tích đo được 10.201% · khung hình chuyển động 13/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

stroke-dashoffsetpathLength=100vector-effect: non-scaling-stroke

Dùng ở đâu

Sau đó một lớp nước màu nhạt đổ đầy, trọn một chu kỳ.

.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

Chuyện gì diễn ra trên màn hình

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.

Nút trượt mũi tên
Mở trong tab mới

05 · Nút trượt mũi tên — Diện tích đo được 2.354% · khung hình chuyển động 15/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

격층 translateXoverflow: hidden$ease-spring

Chỗ dùng

Hợp với nút xem thêm dẫn sang nội dung kế tiếp.

.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

Chuyển động thế nào

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.

Nút hai màu quét
Mở trong tab mới

06 · Nút hai màu quét — Diện tích đo được 10.103% · khung hình chuyển động 16/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

scaleX 레이어 와이프transform-origin: leftcolor 전환

Dùng khi nào

Hợp với nút bắt đầu trên thẻ giá.

.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

Bạn thấy gì

Đú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.

Nút gợn vòng tròn
Mở trong tab mới

07 · Nút gợn vòng tròn — Diện tích đo được 13.521% · khung hình chuyển động 18/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

::after 링 확산scale + opacity:active 트리거

Dùng ở đâu

Trái tim đồng thời phồng lên rồi co nhẹ, rất hợp nút like và bookmark.

.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

Chuyện gì diễn ra trên màn hình

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.

Nút nhấn 3D
Mở trong tab mới

08 · Nút nhấn 3D — Diện tích đo được 5.983% · khung hình chuyển động 12/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

translateY 눌림box-shadow 두께:active 트리거

Chỗ dùng

Dành cho nút bắt đầu game và các nút to trong app trẻ em.

$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

Chuyển động thế nào

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.

Nút pill có loader
Mở trong tab mới

09 · Nút pill có loader — Diện tích đo được 10.092% · khung hình chuyển động 15/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

라벨 opacity 스왑translateX 로더 막대$dur-loop 주기

Dùng khi nào

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.

.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

Nền gradient ở mục 01

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

Khung hình tĩnh khi giảm chuyển động

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.