Kỹ thuật animation CSS dựa trên thời gian
(yuanchuan.dev)- Khi CSS ngày càng hỗ trợ nhiều hàm toán học, cách tiếp cận tính trạng thái animation bằng biến tick thời gian
--tthay cho keyframes lại trở nên thực dụng - Kết hợp
@property --tvới animation gốc dài86400000mscó thể tạo ra thời gian tăng theo mili giây, rồi liên kết nó với các phép tính thuộc tính nhưrotate,translate - Dùng
step()có thể giới hạn frame rate như 8fps, 24fps, 60fps; nhờ tối ưu hóa của trình duyệt, tần suất cập nhật cao hơn 60fps thường cũng không gây vấn đề - Dùng
min(),mod(),sin(),pow(),round()để giới hạn, lặp, dao động và easing giá trị thời gian, qua đó biểu diễn custom easing và dịch chuyển vị trí mà không cần keyframes css-doodlecung cấp các biểu thức rút gọn như@t,@T,@TS, giúp viết các demo nhiều công thức toán ngắn hơn và thử nghiệm nhanh hơn
Tạo tick thời gian làm biến CSS
- Cách tiếp cận trước đây bị giới hạn phạm vi áp dụng vì khó thực hiện tính toán toán học phức tạp trong CSS
- CSS hiện nay hỗ trợ các hàm toán học cần thiết như
mod(),round(), hàm lượng giác, nên có thể thử lại animation dựa trên thời gian - Có thể cần bật Experimental feature flags để xem demo
Cách hoạt động cơ bản
- CSS không thể trực tiếp khởi chạy timer như JavaScript, nhưng có thể dùng CSS Houdini API để định nghĩa biến tùy chỉnh
--tnhằm theo dõi thời gian theo đơn vị mili giây - Ví dụ cơ bản khai báo
@property --tlà<integer>, cho nó thay đổi từ0đến86400000trong@keyframes tick, rồi áp dụng animation86400000ms linear infinitecho:root --ttăng thêm1mỗi 1 mili giây và đạt1000sau 1 giây- Dùng
counter()có thể hiển thị giá trị--thiện tại trên màn hình - Khi liên kết phép tính dựa trên
--tvới thuộc tính, chẳng hạnrotate: calc(var(--t) * .001turn), giá trị sẽ thay đổi theo thời gian và trở thành animation
Điều khiển frame rate
- Với animation mượt, tần suất cập nhật 60fps là đủ
- Nhờ tối ưu hóa render của trình duyệt, tần suất cập nhật cao hơn 60fps thường không gây vấn đề
- Khi cần, có thể dùng step() trong
animation-timing-functionđể trực tiếp giới hạn frame rate- 8fps:
step(calc(86400000/(1000/8))) - 24fps:
step(calc(86400000/(1000/24))) - 60fps:
step(calc(86400000/(1000/60)))
- 8fps:
Biến giá trị thời gian tăng dần thành giá trị animation
- Giá trị
--ttiếp tục tăng theo một chiều - Giá trị
anglevượt quá360degcũng không sao, nhưng không phải thuộc tính CSS nào cũng xử lý giá trị theo kiểu tuần hoàn translate: calc(var(--t) * .001px)sẽ khiến hộp tiếp tục di chuyển sang phải-
Dừng bằng min()
- Có thể dùng min() để dừng ngay khi đạt tới một offset nhất định
translate: min(270px, calc(var(--t) * .5px))giới hạn giá trị dịch chuyển ở 270px- Muốn kiểm soát chính xác thời lượng, có thể giới hạn chính
--t translate: calc(min(3000, var(--t)) * (270px / 3000))- Phép tính này khiến phần tử di chuyển 270px trong 3 giây
-
Lặp bằng mod()
- Dùng mod() nếu muốn hộp di chuyển sang phải rồi bắt đầu lại từ đầu
- Có thể lặp offset bằng
translate: calc(mod(var(--t)/4, 270) * 1px)
-
Tạo chuyển động qua lại bằng sin()
- Chuyển động qua lại có thể biểu diễn bằng sin()
- Liên kết giá trị thời gian với hàm lượng giác như
translate: calc(sin(mod(var(--t)/135, 270)) * 135px)
Hàm custom easing
- Với các hàm toán học CSS và biến
--t, có thể tạo custom easing màcubic-bezier()có thể khó đạt được - Trước hết giới hạn
--tthành giá trị trong khoảng0đến1--t01: calc(min(1000, var(--t)) / 1000)thay đổi từ 0 đến 1 trong 1 giây
-
ease-out-cubic
ease-out-cubicđược cấu thành bởi công thức1 - pow(1 - t, 3)- Sau khi tạo giá trị
--ease-out-cubic, liên kết nó vớitranslate: calc(var(--ease-out-cubic) * 270px)
-
ease-out-elastic
ease-out-elasticđược cấu thành bởi công thứcpow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1- Tạo giá trị
--ease-out-elastictheo cùng cách rồi nhân vớitranslateđể áp dụng cho chuyển động 270px
Rút gọn công thức bằng css-doodle
- Công thức càng phức tạp,
var()vàcalc()càng khiến code khó đọc - Phiên bản mới nhất của css-doodle đã thêm hàm @t biểu diễn
--t, có thể nhận trực tiếp các công thức toán học đơn giản trong đối số - Biểu thức
rotateđược rút gọn như sau- Cũ:
rotate: calc(mod(var(--t) / 1000, 10) * 5deg) css-doodle:rotate: @t(/1000, %10, *5deg)
- Cũ:
- Có thể tạo animation dựa trên toán học bằng code ngắn mà không cần viết keyframes
- Thay đổi tham số là có thể nhanh chóng thử nghiệm kết quả mới
-
@T và @TS
- Ngoài
@t, chữ hoa @T biểu thị time ticks kể từ đầu ngày - @TS là shorthand của
@t(/1000)và theo dõi thời gian theo đơn vị giây - Ví dụ đồng hồ có thể xem trên CodePen
- Kim giây:
rotate: @TS(*6, %360deg) - Kim phút:
rotate: @TS(/60, *6, %360deg) - Kim giờ:
rotate: @TS(/60, /12, *6, %360deg)
- Ngoài
-
Tạo kim giây nhảy bằng round()
- Có thể dùng round() để khiến kim giây chuyển động theo kiểu nhảy từng nấc
- Tham số thứ ba của
round()chỉ định khoảng làm tròn - Trong đồng hồ, mỗi bước là
360 / 60 = 6deg, nên dùngrotate: round(down, @TS(*6, %360deg), 6deg)
Demo và điểm áp dụng
- Ví dụ animation đồng thời màu sắc và vị trí có thể xem trên CodePen
- Ví dụ animation màu chữ có trên CodePen
- Keyframes có thể trông trực quan hơn, nhưng trong các cảnh demo có nhiều phép tính toán học và biến đầu vào, cách dùng thời gian làm biến dễ tạo ra nhiều kết quả đa dạng hơn
- Bản cập nhật ngày 17/05/2024 đã thêm demo animation màu chữ
1 bình luận
Ý kiến trên Hacker News
Nếu muốn điều khiển tiến độ bằng JS thì cũng nên dùng giá trị
animation-delayâmVí dụ
animation-delay: -1500mssẽ khiến animation bắt đầu ngay lập tức nhưng nhảy tới mốc 1,5 giâyNếu điều khiển giá trị này bằng JS thì về cơ bản có thể scrub animation CSS từ JS, đồng bộ toàn bộ animation với vòng lặp tick compute-update-render kiểu game engine
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...
Cách tiếp cận này sẽ nhanh chóng chạm trần khi chỉ cần phức tạp hơn một chút so với hàm easing đơn giản hay keyframe cơ bản cho một hai kênh
Tôi đã dùng Theatre.js trong vài năm qua và khá hài lòng
Đây là thư viện tách thành UI studio để chỉnh sửa keyframe và đường cong Bézier trên timeline, cùng một runtime nội suy các keyframe đó theo timeline
Nếu cần đồng bộ nhiều animation với nhau thì đáng để thử
https://www.theatrejs.com/
Rất ấn tượng, nhưng cũng có cảm giác là cuối cùng chúng ta mới tiệm cận được những gì Flash đã làm được từ vài chục năm trước
Không phải tôi muốn Flash quay lại, nhưng giá mà có những công cụ thân thiện hơn với người dùng để tạo animation CSS
CSS giờ đã hỗ trợ khá đầy đủ các hàm toán học như
mod(),round(), và các hàm lượng giácCSS không thể khởi động timer như JavaScript, nhưng giờ có thể dùng CSS Houdini API để định nghĩa biến tùy chỉnh theo dõi thời gian ở mức mili giây
Nhưng nếu đã có JavaScript thì tại sao lại cần tất cả những thứ này?
Chẳng phải tầng vẽ nên tập trung vào các nguyên thủy vẽ sao? Tại sao lại nhét thêm nhiều tính năng tầng cao hơn vào đó?
Nếu chạy các hàm lượng giác trong JS để làm animation thì có lẽ bạn cũng đang thao tác DOM để cập nhật UI mỗi mili giây, và chi phí đó rất lớn
Việc vẽ do DOM dẫn dắt, DOM này được cấu thành từ HTML hoặc được JS cập nhật
CSS chỉ là một lớp phủ lên trên, truyền tham số cho engine vẽ về việc cần làm gì với DOM và ghi đè các giá trị mặc định
Nó có thể thêm các khối DOM, thay đổi hành vi bố trí bằng các thuật toán phức tạp, và ngay cả margin thực ra cũng không hề đơn giản
Tôi không cho đó là lời giải tuyệt vời, nhưng có vẻ nó bắt đầu từ nhu cầu không phải cập nhật mã HTML chỉ để đổi màu trong lúc render DOM
Có lẽ vì việc cập nhật mã HTML khi đó hoàn toàn không thú vị, nhưng tôi nghĩ ngay từ đầu người ta đã giải sai bài toán
Cũng phải nhớ đó là thời của “webmaster”
Sẽ dễ đọc hơn nếu trong logic fetch không trộn lẫn logic diễn tiến hình học của phần tử DOM loader, chẳng hạn tính góc quay và áp inline style
Tôi biết bây giờ cách để riêng socola và bơ đậu phộng không còn được ưa chuộng, nhưng những quy ước như vậy giúp vạch ra ranh giới để dễ suy luận code nên nằm ở đâu và cần đặt ở đâu
Nói đùa vậy thôi, CSS mang tính khai báo, và chuyện có những bài toán giải được bằng cả JS lẫn CSS là điều tự nhiên
Tôi thực sự háo hức với các tính năng mới được thêm vào CSS gần đây, và việc có ít mã phải thông dịch hơn là điều tốt
CSS
@propertytùy chỉnh dùng trong bài rất tuyệt, và mức hỗ trợ trình duyệt đã là 88% [1]Tuy nhiên cần cẩn thận với khác biệt hành vi giữa các trình duyệt khi đặt
initial-valuedự phòngChrome dùng
initial-valuenếu biến CSS chưa được định nghĩa hoặc được gán giá trị không hợp lệFirefox chỉ dùng
initial-valuekhi biến chưa được định nghĩaVới hầu hết dự án thì không thành vấn đề, nhưng trong một dự án gần đây tôi đã phải đặt giá trị mặc định bằng JavaScript trên Firefox để khớp khác biệt triển khai giữa các trình duyệt
[1] https://caniuse.com/?search=%40property
about:configrồi bậtlayout.css.properties-and-values.enabledLấy giá trị thuộc tính được định nghĩa trong
:root{}:getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");Đặt giá trị thuộc tính:
document.documentElement.style.setProperty("--some-custom-css-var", "some value");Trên các trình duyệt chưa bật hỗ trợ CSS
@property, có thể dùng như một polyfillTrên Android Chrome mới nhất, khá thú vị là các demo trước
mod()vẫn chạy ngay cả khi không bật Experimental feature flags, nhưngmod()vàsin()thì không có tác dụng gìTôi đã muốn có thứ này một thời gian vì muốn làm kiểu box-breathing
Tôi muốn có thể điều chỉnh toàn bộ các giai đoạn hít vào - giữ - thở ra - giữ cho phù hợp với cơ thể và hiệu ứng mong muốn
Nếu không có cách như trên trang này thì tôi không tìm ra cách nào để làm điều đó mà không cần JS, hoặc không rơi vào mức độ phức tạp vô lý
Thật là stylesheet! Các demo trên trang này thực sự rất sáng tạo
Bài viết rất hay
Chrome vẫn chưa phát hành chính thức hỗ trợ CSS
mod(), nên nếu không dùng bản preview của Chrome thì phần lớn ví dụ trên trang sẽ không có animationNếu không cần điều khiển theo tick mà chỉ muốn animation CSS mượt trên mọi trình duyệt thì kỹ thuật FLIP rất hữu ích
https://medium.com/outsystems-experts/flip-your-60-fps-anima...
Animation ở cuối trang rất ấn tượng
Nó trông giống một video thống nhất hơn là các phần tử riêng lẻ
https://RTCode.io là một web playground thời gian thực chạy mà không cần refresh hay reset trạng thái, nên có thể rất hữu ích cho những ai làm việc với animation hay transition CSS
Demo có tại https://RTCode.io
Để nhập một Pen, chỉ cần đổi hostname trong URL thành
xcodepen.io