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

9 cubic bezier css — đường cong copy-paste

Cách ghi thay đổi tốc độ của một chuyển động bằng bốn con số trong CSS chính là giá trị cubic bezier css, còn kiểu nảy tưng tưng thì đựng trong linear().

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

Giữ nguyên một tốc độ thì nút bấm hay tấm thẻ trông cứng đờ, còn nhanh rồi chậm dần hoặc nảy nhẹ ở cuối thì mới tự nhiên. Riêng linear() là bản chuyển từ phương trình lò xo sang đường cong, thứ bốn con số không diễn tả nổi.

Chín mục không xếp theo độ phổ biến mà theo thứ tự cách xử lý giá trị ngày một nhiều lên: ba đường cong giảm tốc mà phần tử lần đầu hiện ra sẽ trải qua (01~03), ba kiểu nảy mang tính vật lý mà cubic-bezier chịu thua (04~06), hai cách tái sử dụng chính những giá trị đó trong code (07, 08), và demo đặt bốn đường cong cạnh nhau để mắt tự so (09).

Số Tên Hàm easing Dùng khi
01 Đường cong ease-out-quart cubic-bezier(.25, 1, .5, 1) modal, drawer mở ra
02 Đường cong ease-in-out-back cubic-bezier(.68, -.6, .32, 1.6) toast, huy hiệu bật ra
03 Đường cong ease-out-expo cubic-bezier(.16, 1, .3, 1) sidebar bung nhanh
04 Nảy bằng linear() linear() lấy mẫu 25 điểm thêm vào giỏ, huy hiệu mới
05 Spring êm zeta .62, omega 7.0 panel cài đặt trượt vào
06 Spring đàn hồi zeta .35, omega 9.0 nút thích, thả kéo
07 Mixin map easing SCSS map.get($easings, $name) design system dùng chung
08 Token --ease Tailwind v4 var(--ease-spring) gọi easing bằng tên
09 Demo so sánh easing dùng lại bốn giá trị trên chọn đường cong bằng mắt

01Đường cong ease-out-quart

css easing functions dùng mục này ở đâu

Gắn cubic-bezier(.25, 1, .5, 1) vào translateX thì vật thể phóng đi thật nhanh ngay từ đầu rồi dừng êm gần đích, không còn chút quán tính.

Đường cong ease-out-quart
Mở trong tab mới

01 · Đường cong ease-out-quart — Diện tích đo được 3.2% · 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

cubic-bezier(.25, 1, .5, 1)transform

Đọc code như thế nào

Rất hợp lúc modal hay drawer mở ra, lúc thẻ hiện lên màn hình.

$duration: $dur-loop;
$easing: cubic-bezier(.25, 1, .5, 1);   // ease-out-quart

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

02Đường cong ease-in-out-back

Dùng ở đâu

Giá trị y của cubic-bezier(.68, -.6, .32, 1.6) vượt ra ngoài khoảng 0 tới 1.

Đường cong ease-in-out-back
Mở trong tab mới

02 · Đường cong ease-in-out-back — Diện tích đo được 6.664% · 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

cubic-bezier오버슈트transform

Điểm chính của code

Vì thế sinh ra hai động tác chuẩn bị: kéo lùi một chút trước khi xuất phát, rồi vượt quá đích và quay ngược lại sau khi đã tới. Hợp lúc toast hay huy hiệu bật ra.

$easing: cubic-bezier(.68, -.6, .32, 1.6);   // ease-in-out-back

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

03Đường cong ease-out-expo

Bạn nhìn thấy gì

cubic-bezier(.16, 1, .3, 1) xong gần hết quãng đường chỉ trong vài khung hình đầu, đoạn còn lại bò chậm tới mức mắt thường gần như không thấy chuyển động, kéo phần cập bến ra dài.

Đường cong ease-out-expo
Mở trong tab mới

03 · Đường cong ease-out-expo — Diện tích đo được 5.573% · khung hình chuyển động 21/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

cubic-bezier급감속transform

Nguyên lý chạy thế nào

Dùng khi bạn muốn nhấn vào cảm giác tốc độ lúc sidebar bung ra.

$easing: cubic-bezier(.16, 1, .3, 1);   // ease-out-expo

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

04Nảy bằng linear()

Đó là tình huống gì

cubic-bezier là đường cong chỉ lên xuống đúng một lần, nên không dựng nổi kiểu quả bóng nảy thêm mấy nhịp trên mặt sàn. Lấy mẫu công thức easeOutBounce của Robert Penner thành 25 điểm rồi đổ vào linear() thì ra đường cong nảy thật, giá trị cứ tăng lên rồi giảm đi lặp lại.

Nảy bật bằng linear()
Mở trong tab mới

04 · Nảy bật bằng linear() — Diện tích đo được 3.203% · 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

linear()표본점 25개transform

Cách tạo ra nó

Dùng lúc icon rơi vào giỏ hàng, lúc một huy hiệu thông báo mới hiện lên.

$easing: linear(0, .013, .053, .118, .21, .328, .473, .643, .84, .97, .875, .807, .766, .75, .761, .798, .861, .951, .973, .943, .939, .962, .995, .984, 1);

.stage.is-demo .puck {
  animation: drop $duration $easing infinite;
}
@keyframes drop {
  from { transform: translate(-50%, 0); }
  to   { transform: translate(-50%, $drop); }
}

05Spring êm

Điều gì đang diễn ra

Lấy mẫu 23 điểm từ phương trình lò xo giảm chấn x(t) = 1 - e^(-ζωt)(cos(ω_d t) + (ζω/ω_d)sin(ω_d t)) với hệ số giảm chấn (zeta) .62 và tần số góc (omega) 7.0, bạn được dãy giá trị chỉ vượt đích chừng 8% rồi lắng xuống rất nhanh; hệ số giảm chấn càng tiến gần 1 thì phần vượt đích càng teo lại.

Lò xo dịu dàng
Mở trong tab mới

05 · Lò xo dịu dàng — Diện tích đo được 3.44% · 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

linear()감쇠 스프링transform

Vì sao làm theo cách này

Vừa vặn với những chuyển cảnh UI thường ngày, như panel cài đặt trượt vào từ bên hông.

$easing: linear(0, .044, .153, .297, .452, .602, .736, .849, .937, 1.002, 1.045, 1.071, 1.082, 1.083, 1.077, 1.066, 1.053, 1.04, 1.028, 1.018, 1.009, 1.003, 1);

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

06Spring đàn hồi

Trên màn hình có gì

Vẫn phương trình đó nhưng hạ hệ số giảm chấn xuống .35 và nâng tần số góc lên 9.0, dãy giá trị vượt đích tới hơn 30% (1.307) rồi dao động qua lại thêm vài lượt, độ đàn hồi hiện rõ.

Lò xo đàn hồi rõ nét
Mở trong tab mới

06 · Lò xo đàn hồi rõ nét — Diện tích đo được 6.622% · 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

linear()감쇠 스프링오버슈트

Cốt lõi khi triển khai

Dùng lúc bạn bấm nút thích, lúc thả tay sau khi kéo và thẻ bật ngược về.

$easing: linear(0, .055, .197, .394, .612, .824, 1.009, 1.153, 1.25, 1.3, 1.307, 1.281, 1.231, 1.167, 1.099, 1.035, .982, .942, .916, .905, .907, .917, .935, .955, .976, .995, 1);

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

07Mixin map easing SCSS

Trên màn hình trông thế nào

Để khỏi chép tay giá trị đường cong ở từng bài, mấy giá trị đã dùng được gom theo tên vào map $easings, rồi @mixin ease($name) móc ra bằng map.get.

Map $easings và mixin SCSS
Mở trong tab mới

07 · Map $easings và mixin SCSS — Diện tích đo được 9.358% · 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

Sass map@mixinmap.get

Mấu chốt nằm ở đâu

Đây là pattern của các design system nơi nhiều component phải dùng chung một bộ easing thương hiệu.

$easings: (
  "ease-out-quart":   cubic-bezier(.25, 1, .5, 1),
  "ease-in-out-back": cubic-bezier(.68, -.6, .32, 1.6),
  "spring-gentle":    linear(0, .044, .153, .297, .452, .602, .736, .849, .937, 1.002, 1.045, 1.071, 1.082, 1.083, 1.077, 1.066, 1.053, 1.04, 1.028, 1.018, 1.009, 1.003, 1),
  "spring-snappy":    linear(0, .055, .197, .394, .612, .824, 1.009, 1.153, 1.25, 1.3, 1.307, 1.281, 1.231, 1.167, 1.099, 1.035, .982, .942, .916, .905, .907, .917, .935, .955, .976, .995, 1),
);

@mixin ease($name, $prop: animation-timing-function) {
  #{$prop}: map.get($easings, $name);
}

.stage.is-demo .puck {
  animation-name: move;
  animation-duration: $duration;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  @include ease("spring-snappy");   // lấy từ map đúng giá trị của mục 06
}

08Token --ease của Tailwind v4

Khi nào dùng

Tailwind v4 cho phép đăng ký --ease-* trong khối @theme, sau đó gọi easing bằng tên như một utility class.

Token --ease Tailwind v4
Mở trong tab mới

08 · Token --ease Tailwind v4 — Diện tích đo được 6.524% · 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

CSS 커스텀 프로퍼티var()Tailwind v4

Điểm cần nhớ

File zip này không kéo Tailwind về để tránh phụ thuộc bên ngoài, nhưng vẫn tái hiện y hệt pattern đó chỉ bằng custom property trên :root.

$easing: var(--ease-spring);

:root {
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
}
.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}

09Demo so sánh easing

Đó là màn hình nào

Cho bốn đường cong quart (01), back (02), spring snappy (06) và bounce (04) chạy song song cùng 2 giây, cùng quãng đường, dáng giảm tốc của chúng tách bạch ngay trước mắt bạn.

Demo so sánh easing
Mở trong tab mới

09 · Demo so sánh easing — Diện tích đo được 4.24% · 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

cubic-bezierlinear()비교

Làm ra nó thế nào

Ở đây không dựng giá trị mới nào, chỉ dùng lại đúng giá trị của bốn mục phía trên.

.puck--quart  { animation: move $duration cubic-bezier(.25, 1, .5, 1) infinite alternate; }
.puck--back   { animation: move $duration cubic-bezier(.68, -.6, .32, 1.6) infinite alternate; }
.puck--spring { animation: move $duration linear(0, .055, .197, .394, .612, .824, 1.009, 1.153, 1.25, 1.3, 1.307, 1.281, 1.231, 1.167, 1.099, 1.035, .982, .942, .916, .905, .907, .917, .935, .955, .976, .995, 1) infinite alternate; }
.puck--bounce {
  animation: move-once $duration linear(0, .013, .053, .118, .21, .328, .473, .643, .84, .97, .875, .807, .766, .75, .761, .798, .861, .951, .973, .943, .939, .962, .995, .984, 1) infinite;
}

Dễ vỡ ở đâu — track dọc tràn sớm hơn track ngang

Vấn đề nằm ở đâu

Mục 04 (nảy bằng linear()), thứ mà những ví dụ cubic bezier css examples thông thường không dựng nổi, ban đầu được cho rơi 180px chứ không phải 132px. Trên khung desktop 480×300 thì vẫn ổn. Vấn đề nằm ở cách site này đo hiển thị trên mobile: bóp bề ngang về 320px thì chiều cao cũng co theo đúng tỷ lệ (320×300÷480) xuống còn 200px. Track xếp dọc 180px cộng thêm lề trên dưới là vượt quá 200px; render thật rồi đo lại thì đúng là ra phần tràn.

Tránh như thế nào

Track nằm ngang còn dư chỗ trong bề rộng 320px nên thoát, riêng track dọc đụng ngay ngân sách chật hơn hẳn là chiều cao 200px; hạ track xuống 132px thì phần tràn mới biến mất. Mật khẩu được hỏi khi mở file zip là wnxge56e, nhập theo đúng hướng dẫn là bung được. Lúc thêm animation chạy theo chiều dọc, người ta rất dễ chỉ kiểm tra responsive theo bề ngang rồi bỏ qua, nhưng như bài này cho thấy, chiều cao của viewport mobile phải đo riêng.

Khả năng tiếp cận

Còn lại gì khi bật reduced-motion

Cả chín mục đều rơi về animation: none dưới prefers-reduced-motion: reduce, đồng thời ghim transform vào đúng giá trị điểm đến — mục 01, 02, 03, 05, 06, 07, 08 dừng ở cuối track (196px hoặc 188px), mục 04 dừng ở điểm rơi (108px), mục 09 thì cả bốn làn đều dừng ở điểm cuối của riêng mình. Chuyển động tắt đi nhưng khung hình tĩnh không trông như bị kẹt giữa chừng.

Ngoài ra nên chú ý

Mục đích của chính bài này là phô khác biệt hình dáng giữa các đường cong nên bật reduced-motion thì bạn không cảm được khác biệt đó, bù lại thông tin vị trí cuối vẫn còn nguyên và bố cục không hề vỡ.

Hỏi đáp (FAQ)

css transition timing function kiểu cubic-bezier khác linear() chỗ nào?

cubic-bezier() là một đường cong trơn được định nghĩa bằng bốn con số, nên nó chịu thua những chuyển động mà giá trị đổi chiều nhiều lần, kiểu quả bóng nảy thêm mấy nhịp. linear() thì liệt kê bao nhiêu điểm tùy ý để vẽ nên đường cong, nhờ vậy các giá trị lấy mẫu thẳng từ phương trình vật lý như mục 04, 05, 06 cũng bê nguyên vào được.

CSS có sẵn từ khóa riêng cho spring không?

Không có. Hàm easing chuyên dụng kiểu spring() tới giờ vẫn chưa thành chuẩn trong trình duyệt. Muốn có chuyển động trông như lò xo bằng CSS lúc này, cách thực tế là tự tính phương trình lò xo giảm chấn rồi lấy mẫu vào linear(), đúng như mục 05 và 06 đã làm.

Có cách nào lấy được mấy giá trị này mà khỏi tự tính không?

Với cubic-bezier(), bạn chép giá trị từ những danh sách preset đặt tên sẵn như easings.net cũng được, còn bảng animation trong developer tools của Chrome hay Firefox cho bạn vừa chỉnh đường cong bằng mắt vừa đọc ngay con số tương ứng. Riêng dãy mẫu của linear() thì giải thẳng phương trình bằng Python hay JavaScript như mục 04 tới 06 mới chuẩn về vật lý. Cú pháp của hai hàm nằm trong tài liệu easing-function của MDN. Các animation CSS khác bạn tìm ở trang tìm kiếm, còn cách blog này dựng demo thì có ở trang giới thiệu.

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.