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

9 css bounce animation — dán một dòng là chạy

css bounce animation là chuyển động cho phần tử nổi lên, nảy, rung và đung đưa như đang sống.

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

Chín mục không xếp theo bảng chữ cái mà theo lộ trình một cái nút đi qua trên màn hình. Đầu tiên là báo mình có mặt (01), rồi nài người ta bấm vào (02, 03), thở khe khẽ ở một góc (04, 05), vẫy tay mời xem thêm (06), trang trí nền phía sau (07), lấp lánh khen bạn chọn đúng (08) và trượt sang màn hình kế tiếp (09). Bạn hãy lướt bảng duration và trigger trước, rồi mở code thật ở từng mục.

Class Cái gì chuyển động duration Trigger
.o-float translateY·rotate 2.8s luôn chạy
.o-poyon scale 550ms hover, focus
.o-puru scale ngang·dọc 900ms luôn chạy
.o-yura rotate 2.4s luôn chạy
.o-kuru rotate 1.6s luôn chạy
.o-pyon translateY·scale 900ms luôn chạy
.o-gunya border-radius 3.2s luôn chạy
.o-kira opacity·scale 1.8s luôn chạy
.o-nuru translateX·scale 700ms hover, focus

01Bồng bềnh trôi nổi (fuwafuwa)

Màn hình trông thế nào

Chỉ gắn .o-float là chiếc badge đã lên xuống chậm rãi bằng translateY kèm một chút rotate.

Bồng bềnh trôi (fuwafuwa)
Mở trong tab mới

01 · Bồng bềnh trôi (fuwafuwa) — Diện tích đo được 3.606% · 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

translateYrotate 3deg2.8s

Nguyên lý hoạt động

Dùng cho badge hàng mới hay chip thông báo khuyến mãi, thứ phải báo mình có mặt ở một góc màn hình.

$duration: 2.8s;
$easing: ease-in-out;
$color: $subject-blue;

.o-float {
  animation: o-float $duration $easing infinite;
}

@keyframes o-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-14px) rotate(-3deg); }
}

02Nảy đàn hồi (poyon)

Hiệu ứng hover cho nút dùng ở đâu

.o-poyon chạy một lần khi bạn đưa chuột lên hoặc focus bằng bàn phím, scale bị nén xuống rồi bật to ra theo easing đàn hồi.

Nảy bật đàn hồi (poyon)
Mở trong tab mới

02 · Nảy bật đàn hồi (poyon) — Diện tích đo được 5.788% · khung hình chuyển động 10/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

scale overshootelastic550ms

Cách dựng

Dùng cho nút thêm vào giỏ hay nút thích, loại nút phải khiến người ta muốn bấm.

$duration: 550ms;
$easing: cubic-bezier(.34, 1.56, .64, 1);
$color: $subject-cream;

.btn.o-poyon:hover,
.btn.o-poyon:focus-visible { animation: o-poyon $duration $easing 1; }

@keyframes o-poyon {
  0%   { transform: scale(1, 1); }
  30%  { transform: scale(.84, 1.09); }
  55%  { transform: scale(1.20, .89); }
  75%  { transform: scale(.94, 1.03); }
  100% { transform: scale(1, 1); }
}

03Thạch rung rinh (purupuru)

Dùng ở đâu

.o-puru lặp lại scale ngang và dọc với biên độ rất nhỏ và lệch pha nhau.

Rung như thạch (purupuru)
Mở trong tab mới

03 · Rung như thạch (purupuru) — Diện tích đo được 0.772% · 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

scale(1.07, .94)어긋난 위상900ms

Vì sao dựng như vậy

Nó tạo cảm giác icon trái tim lưu tin hay chuông thông báo cứ rung li ti như đang xin bạn chạm vào.

$duration: 900ms;
$easing: ease-in-out;
$color: $subject-cream;

.o-puru {
  animation: o-puru $duration $easing infinite;
}

@keyframes o-puru {
  0%, 100% { transform: scale(1, 1); }
  25%      { transform: scale(1.07, .94); }
  50%      { transform: scale(1, 1.07); }
  75%      { transform: scale(.93, 1); }
}

04Đung đưa nhè nhẹ (yurayura)

Điều mắt thấy

.o-yura ghim transform-origin lên cạnh trên rồi chỉ xoay qua lại thật chậm bằng rotate.

Đung đưa nhẹ (yurayura)
Mở trong tab mới

04 · Đung đưa nhẹ (yurayura) — Diện tích đo được 1.628% · khung hình chuyển động 22/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

rotatetransform-origin: top2.4s

Cốt lõi phần dựng

Dùng khi muốn thẻ giảm giá treo lơ lửng hay chuông thông báo trông như đang lay trong gió.

$duration: 2.4s;
$easing: ease-in-out;
$color: $subject-blue;

.o-yura {
  animation: o-yura $duration $easing infinite;
  transform-origin: top center;
}

@keyframes o-yura {
  0%, 100% { transform: rotate(-6deg); }
  50%      { transform: rotate(6deg); }
}

05Xoay tròn (kurukuru)

Tình huống nào

.o-kuru không đẩy trọn 360 độ bằng linear mà đặt các điểm đi qua lệch nhau ở từng chặng để tạo nhịp hẫng một phách. Dùng cho icon tải lại hay đồng bộ.

Xoay vòng (kurukuru)
Mở trong tab mới

05 · Xoay vòng (kurukuru) — Diện tích đo được 0.403% · 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

rotate 360deg구간별 easing1.6s

Điểm mấu chốt là gì

$duration: 1.6s;
$easing: cubic-bezier(.65, 0, .35, 1);
$color: $subject-cream;

.o-kuru {
  animation: o-kuru $duration $easing infinite;
}

@keyframes o-kuru {
  0%   { transform: rotate(0deg); }
  55%  { transform: rotate(230deg); }
  80%  { transform: rotate(330deg); }
  100% { transform: rotate(360deg); }
}

06Nhảy tưng tưng (pyonpyon)

Chuyện gì xảy ra

.o-pyon nén scale dọc cho bẹt xuống ngay trước lúc tiếp đất, rồi kéo translateY cùng scale lên lúc bật nhảy để tạo nhịp tưng tưng.

Tưng tưng nhảy (pyonpyon)
Mở trong tab mới

06 · Tưng tưng nhảy (pyonpyon) — Diện tích đo được 5.925% · 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

translateY+scale착지 스쿼시900ms

Điều đáng lưu ý

Dùng cho mũi tên "xem thêm" hay icon mời cuộn xuống.

$duration: 900ms;
$easing: ease-in-out;
$color: $subject-blue;

.o-pyon {
  animation: o-pyon $duration $easing infinite;
}

@keyframes o-pyon {
  0%, 100% { transform: translateY(0)     scale(1, 1); }
  10%      { transform: translateY(2px)   scale(1.11, .9); }
  40%      { transform: translateY(-16px) scale(.93, 1.08); }
  70%      { transform: translateY(2px)   scale(1.11, .9); }
  85%      { transform: translateY(0)     scale(1.02, 1.03); }
}

07Uốn mềm oặt (gunyagunya)

Trên màn hình diễn ra gì

.o-gunya đổi bốn góc border-radius theo bốn nhịp khác nhau ngay trong keyframes, khiến khối blob nền vừa trôi vừa biến dạng mềm oặt.

Uốn mềm dẻo (gunyagunya)
Mở trong tab mới

07 · Uốn mềm dẻo (gunyagunya) — Diện tích đo được 1.768% · 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

border-radius 4분할비대칭 keyframes3.2s

Cách tạo ra nó

Dùng cho nền hero hay hình khối nhấn nằm sau card giới thiệu tính năng.

$duration: 3.2s;
$easing: ease-in-out;
$color: $subject-cream;

.o-gunya {
  animation: o-gunya $duration $easing infinite;
}

@keyframes o-gunya {
  0%, 100% { border-radius: 50% 63% 68% 63% / 50% 37% 32% 37%; transform: rotate(0deg); }
  20%      { border-radius: 67% 66% 56% 42% / 33% 34% 44% 58%; transform: rotate(5deg); }
  45%      { border-radius: 56% 42% 33% 34% / 44% 58% 67% 66%; transform: rotate(2deg); }
  70%      { border-radius: 33% 34% 44% 58% / 67% 66% 56% 42%; transform: rotate(-5deg); }
  95%      { border-radius: 44% 58% 67% 66% / 56% 42% 33% 34%; transform: rotate(-2deg); }
}

08Lóe sáng (kira)

Nhìn thấy thế nào

.o-kira đặt hai ngôi sao nhỏ dựng bằng ::before và ::after, cho mỗi cái một delay riêng để chúng thay phiên lóe lên bằng scale với opacity.

Lóe lấp lánh (kira)
Mở trong tab mới

08 · Lóe lấp lánh (kira) — Diện tích đo được 0.399% · 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

scale+opacity::before/::after1.8s

Đọc code thế nào

Giúp badge gợi ý hay nhãn tính năng mới lóe sáng theo chu kỳ mà hút mắt người xem.

$duration: 1.8s;
$easing: ease-in-out;
$color: $subject-blue;

.badge.o-kira::before { animation: o-kira $duration $easing infinite; }
.badge.o-kira::after  { animation: o-kira $duration $easing infinite; animation-delay: .4s; }

@keyframes o-kira {
  0%, 45%, 100% { opacity: 0; transform: scale(.4); }
  62%           { opacity: 1; transform: scale(1.3); }
  78%           { opacity: 0; transform: scale(.6); }
}

09Trơn mượt (nuru)

Dùng khi nào

Khi bạn hover hoặc focus, .o-nuru cho mũi tên trượt đi mượt bằng một cubic-bezier gần với ease-out-expo.

Trượt mượt mà (nurutto)
Mở trong tab mới

09 · Trượt mượt mà (nurutto) — Diện tích đo được 0.172% · 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

translateXcubic-bezier(.16, 1, .3, 1)700ms

Điểm chính của code

Cảm giác trơn mượt này trái ngược hẳn độ đàn hồi của tám mục còn lại nên hợp với link "xem chi tiết".

$duration: 700ms;
$easing: cubic-bezier(.16, 1, .3, 1);
$color: $subject-blue;

.link.o-nuru:hover .link__arrow,
.link.o-nuru:focus-visible .link__arrow { animation: o-nuru $duration $easing 1; }

@keyframes o-nuru {
  0%   { transform: translateX(0)    scale(1); }
  100% { transform: translateX(28px) scale(1.15); }
}

Dễ vỡ ở đâu — cái bẫy khi quên xóa class is-demo

Vấn đề là gì

Những mục chạy bằng hover như 02 và 09, chứ không phải bằng cú click, đặt chung trên một phần tử cả quy tắc dùng thật (:hover, :focus-visible) lẫn quy tắc vòng lặp .is-demo để trang này tự phát. Hai quy tắc có độ đặc hiệu ngang nhau (hai ba class, hòa điểm) nên bên khai báo sau sẽ thắng. Vậy nên nếu bạn mang .is-demo lên màn hình thật mà quên gỡ, người dùng đưa chuột lên sẽ thấy vòng lặp demo 2 giây thay cho cú chạy ngắn của trigger. Khi chép code từ zip vào dự án, bạn chỉ cần xóa class is-demo; ngược lại muốn giữ is-demo thì phải viết quy tắc dùng thật xuống phía dưới trong stylesheet.

Tránh thế nào

Tra css bounce effect hay button bounce effect css thì được hàng loạt kết quả, nhưng ở đây bạn dán một tệp là chạy ngay không cần tuyển chọn. Trong chín mục, mục 07 uốn mềm oặt có cường độ trung bình đo được 42.9, thấp nhất cả nhóm, vì đường cong dựng chỉ bằng border-radius nên độ chênh với nền rất thoải. Đặt nó lên nền tương phản yếu thì gần như không nhìn ra, vậy hãy chọn chỗ có màu khác hẳn nền. Bộ zip gói chín bản vanilla và React đã khớp sẵn duration, easing cùng màu sắc, và nó mở bằng đúng mật khẩu nén kxj6u9k6 ghi ở đoạn này.

Khả năng tiếp cận

Bật reduced-motion còn lại gì

Nếu hệ điều hành bật chế độ giảm chuyển động, cả chín mục đều xóa chuyển động bằng animation: none trong khối @media (prefers-reduced-motion: reduce). Những mục chỉ trang trí nền như 01 bồng bềnh, 04 đung đưa, 07 uốn mềm có tắt đi cũng không mất thông tin nào; còn 02 poyon và 09 nuru dù mất animation thì chức năng thật là bấm nút hay chuyển link vẫn nguyên. Danh sách trình duyệt hỗ trợ media query này nằm ở bảng tương thích cuối tài liệu prefers-reduced-motion của MDN.

@media (prefers-reduced-motion: reduce) {
  .o-float, .o-puru, .o-yura,
  .o-kuru, .o-pyon, .o-gunya { animation: none; }
  .badge.o-kira::before, .badge.o-kira::after { animation: none; }
  .btn.o-poyon:hover, .btn.o-poyon:focus-visible,
  .link.o-nuru:hover .link__arrow,
  .link.o-nuru:focus-visible .link__arrow { animation: none; }
}

Bài nên đọc tiếp

Các bộ khác cùng loạt bài nằm ở danh mục CSS animation, còn nếu lần đầu ghé trang này, bạn xem qua trang giới thiệu trước.

FAQ

Dùng cả chín animation lặp vô hạn trên một màn hình có được không?

Về kỹ thuật thì được, nhưng tôi khuyên chỉ nên hai ba mục một màn hình. Mục 02 poyon và 08 lóe sáng cùng đóng vai "nhìn chỗ này đi" nên đặt chung một màn hình sẽ khiến mắt người xem bị chia ra hai hướng. Ngược lại, mục yên ắng như 01 bồng bềnh và mục báo trạng thái như 05 xoay tròn không giẫm vai nhau nên đặt cùng nhau vẫn ổn.

Tại sao trên màn hình demo mục 02 và 09 cứ chạy mãi?

Ở màn hình thật, 02 và 09 là trigger hover, chỉ chạy khi bạn đưa chuột lên hoặc focus. Demo trên trang này phải trình diễn chuyển động dù không ai chạm vào, nên tôi ép nó tự phát mỗi 2 giây bằng class .is-demo; lúc chép vào code thật, bạn xóa class đó đi là xong.

Trong React tái hiện trigger hover ra sao?

Thư mục react/ trong zip nhận duration, easing, color qua props rồi đẩy sang biến CSS, và các mục chạy bằng hover như 02, 09 cũng xử lý thẳng bằng selector :hover và :focus-visible của CSS module, không cần state. Những mục lặp liên tục như 01 và 03 đến 08 thì animation chạy ngay từ lúc component được mount.

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.