Cubic Bezier chuyên sâu: Làm chủ đường cong chuyển động
Mỗi khi viết transition: transform 0.3s ease-out, bạn đang dùng một shorthand ẩn một hàm toán học phức tạp hơn. Hiểu hàm đó từ gốc rễ cho phép bạn tạo ra bất kỳ cảm giác chuyển động nào thay vì chọn từ năm preset có sẵn.
Bezier curve là gì
Cubic bezier là đường cong xác định bởi bốn điểm: P0 (start), P1 (handle 1), P2 (handle 2), P3 (end). Trong CSS, P0 luôn là (0, 0) và P3 luôn là (1, 1) — chỉ có P1 và P2 thay đổi.
cubic-bezier(x1, y1, x2, y2)
/* P1 P2 */
Trục X là thời gian (0 = bắt đầu, 1 = kết thúc). Trục Y là progress của animation (0 = vị trí ban đầu, 1 = vị trí đích). Curve mô tả tốc độ thay đổi của progress theo thời gian — không phải vị trí.
Đọc một curve
ease-out: cubic-bezier(0, 0, 0.58, 1)
- P1 = (0, 0): handle đầu nằm ở góc origin → animation bắt đầu nhanh
- P2 = (0.58, 1): handle cuối nằm cao và lệch trái → animation chậm dần sớm
ease-in: cubic-bezier(0.42, 0, 1, 1)
- P1 = (0.42, 0): handle đầu lệch phải mạnh → animation bắt đầu chậm
- P2 = (1, 1): handle cuối ở góc end → không có deceleration
Tại sao y > 1 tạo overshoot
Khi y của P1 hoặc P2 vượt quá 1.0, curve tạo ra giá trị output > 1.0 — tức là animation vượt qua endpoint trước khi quay về. Đây là cách CSS giả lập spring:
/* Spring — overshoot ~17% */
cubic-bezier(0.34, 1.56, 0.64, 1)
Y = 1.56 tạo overshoot khoảng 56% vượt qua khoảng cách từ start đến end. Với transform: translateX(100px), element sẽ đi đến ~156px rồi quay về 100px.
Tương tự, y < 0 tạo undershoot — animation đi ngược chiều trước khi tiến về đích. Dùng cho “pull back before launch” effect.
Năm preset của CSS và cảm giác của chúng
| Preset | Curve | Cảm giác |
|---|---|---|
linear | (0, 0, 1, 1) | Robot, không tự nhiên |
ease | (0.25, 0.1, 0.25, 1) | Browser default, hơi lazy |
ease-in | (0.42, 0, 1, 1) | Chậm rồi nhanh, dùng cho exit |
ease-out | (0, 0, 0.58, 1) | Nhanh rồi chậm, dùng cho enter |
ease-in-out | (0.42, 0, 0.58, 1) | Cân bằng, dùng cho loop |
Vấn đề: cả năm preset đều “generic”. Chúng không truyền đạt tính cách của sản phẩm.
Tạo custom curve theo cảm giác mục tiêu
Snappy / crisp — phản hồi tức thì, settle ngay:
cubic-bezier(0.2, 0, 0, 1)
P1 kéo mạnh sang phải (bùng phát nhanh), P2 nằm thấp bên trái (decelerate đột ngột).
Gentle / soft — từ tốn, không vội:
cubic-bezier(0.4, 0, 0.6, 1)
Cả hai handle nằm gần center, curve nhẹ nhàng đều đặn.
Aggressive / dramatic — bắt đầu chậm, kết thúc bùng nổ:
cubic-bezier(0.8, 0, 0.2, 1)
P1 rất xa bên phải (delay lớn trước khi bắt đầu), P2 gần bên trái (tăng tốc mạnh ở cuối).
Material Design standard — balance giữa snappy và gentle:
cubic-bezier(0.2, 0, 0, 1) /* standard */
cubic-bezier(0.05, 0.7, 0.1, 1) /* emphasized */
Điểm khác biệt với spring
Cubic bezier luôn có duration cố định. Dù curve phức tạp đến đâu, animation kết thúc đúng lúc transition-duration chỉ định. Spring không có duration cố định — nó settle khi velocity gần bằng 0, thời gian phụ thuộc vào stiffness và damping.
Bezier tốt cho: fade, color, layout shift, page transition — những thứ cần predictable timing. Spring tốt cho: interactive response, direct manipulation — những thứ cần feel vật lý.
Xem thêm trong Ease vs Spring và Spring Parameters: mass, stiffness, damping.
Tools
Chrome DevTools: click vào icon curve bên cạnh transition-timing-function, kéo handle trực quan.
cubic-bezier.com: preview realtime với side-by-side comparison.
Tip: luôn test curve ở speed 0.25× trong DevTools — slow motion bộc lộ ngay cả những artifact nhỏ nhất.
Sai lầm thường gặp
Dùng linear cho loading animation: Linear trông máy móc. Dùng ease-in-out cho shimmer/pulse.
Duration quá dài với ease-out: ease-out 0.8s trông như lag. Thường 150–300ms là đủ cho UI element.
Dùng cùng một curve cho enter và exit: Enter nên là ease-out (nhanh vào, chậm settle). Exit nên là ease-in (chậm bắt đầu, nhanh biến mất) — đây là cách mắt người tracking chuyển động.
Xem cách các nguyên tắc này áp dụng trong thực tế: Hover Micromotion, Scroll Reveal Patterns, Page Transition Patterns.