Không cần đến CSS, Web Animation API còn làm web sống động hơn nữa
Một chìa (API) đút mọi lỗ
Animation trên web từ lâu đã được chia thành 4 lĩnh vực cụ thể:
- CSS transitions and animations hiệu năng cao và có cung cấp keyframing, nhưng cũng rất mất thời gian build, và chỉ cung cấp start-and-end control cơ bản trong CSS và JavaScript. Việc này dễn đến sự giới hạn ở các animation UI phản hồi đơn giản, vòng lặp, và animation page load.
- SMIL (Synchronized Multimedia Integration Language) cũng rất mạnh mẽ, nhưng lại quá nặng về cú pháp và hỗ trợ trình duyệt không hoàn chỉnh. Công cụ còn giới hạn đến controlling elements chỉ trong bối cảnh SVG.
- JavaScript cho phép điều khiển elements trực tiếp, nhưng lại không thấu triệt các hàm ‘thân thiện với designer’ như keyframes hoặc easing, thiếu native optimization và hiệu năng không tốt như CSS. Canvas API animation rất tuyệt vời, nhưng lại không thấu triệt nguyên lý nền tảng của animation, và không thể animate các DOM elements độc đoán (arbitrary).
- JavaScript animation frameworks như Greensock, với mong muốn khác phục những điểm thiếu hụt của animation trong JavaScript, nhưng lại có tất cả các điểm yếu hay có ở frameworks: page load, hiệu năng, và học cú pháp mới.
Web Animations API lại có hoài bão to lớn hơn: kết hợp các tính năng tốt nhất của tất cả các kiểu công cụ trên thành một chỉnh thể duy nhất, đồng thời loại bỏ hết mọi thiếu sót, tạo sự thấu hiểu thấu đáo về keyframes, easing, và element control trong JavaScript, với cùng hiệu năng on-screen như CSS. Với các specifications giờ đây đã được hỗ trợ trong Chrome và Firefox, cùng với các nghiên cứu đã được công bố và đã thực hiện với các trình duyệt khác, gồm có Safari và Edge. Cùng với robust polyfil khả dụng. Với những ưu việt như vậy, đã đến lúc ta dành sự chú ý xứng đáng cho Web Animations API.
The Web Animations API helps make animation a staple of web design, opening the gates to vendor-optimized performance and 3rd party tooling.
— Rachel?Nabors (@rachelnabors) September 29, 2016
Keyframes trong JavaScript
Hãy xét đến một trong những ví dụ đơn giản nhất về keyframe animation: duy chuyển một element red ball (banh đỏ) từ phía này sang phía kia page. Cho dù ta có dùng kỹ thuật nào đi nữa, element sẽ giữ nguyên như cũ:
1 | <div id="redball"></div> |
CSS ban đầu cũng vậy:
1 2 3 4 5 6 7 8 9 10 11 12 13 | body { margin: 0; background: #000; overflow: hidden; min-height: 100vh; } #redball { background: red; width: 30vmin; height: 30vmin; border-radius: 50%; } |
Tôi đã dùng đến đơn vị vmin
để element luôn đối xứng , đồng thời phản hồi với kích thước của view point.
Trong CSS, để di chuyển banh từ bên này sang bến khác page, ta sẽ cần:
1 2 3 4 5 6 7 8 | @keyframes moveBall { from { transform: translateX(-20vw); } to { transform: translateX(100vw); } } |
Animation sẽ được call sau khi khai báo red ball element:
1 2 3 | #redball { animation: moveBall 3s infinite; } |
Kết quả minh họa trên codepen:
See the Pen Basic CSS Red Ball Animation by SitePoint (@SitePoint) on CodePen.
Đến đây, có một số điều cần ghi nhớ về animation, trong đó, easing (tăng tốc lúc ban đầu và chậm lại khi về cuối) được built in tự động.
Về Web Animations API
Giữ nguyên HTML và style ban đầu, hãy xóa bỏ CSS animation và thay thế JavaScript (sử dụng Web Animation API) để đạt mục đích tương tự:
1 2 3 4 5 6 7 8 9 | var moveBall = document.getElementById('redball').animate([{ transform: 'translateX(-20vw)' }, { transform: 'translateX(100vw)' }], { duration: 3000, iterations: Infinity, easing: 'ease' }); |
Bạn có thể thấy rằng animation tiếp nhận cú pháp đa phần giống với CSS, nhưng lại biểu hiện dưới dạng object với một array thể hiện keyframes. Chúng ta không phải khai báo rõ ràng to
hay from
trong keyframe – JavaScript sẽ tự động phân phối đều keyframe cho chúng ta, thậm chí declaration (khai báo) cũng hoàn toàn có thể.
Animation vẫn chưa chạy; giống như trong CSS, chúng ta phải call ra:
1 | moveBall.play(); |
Phần keyframe của cú pháp thậm chí sẽ trở nên dễ dàng hơn nữa với các trình duyệt tương lai, khi các khía cạnh của cú pháp chuyển đổi CSS (mà trước đó là values) có thể dùng được làm properties:
1 | translateX: -20vw; |
Thay đổi đến specification này đã có sẵn trong Chrome Canary, nhưng phải đến một hai nắm thì may ra cú pháp mới mới được tất cả trình duyệt hiện đại hỗ trợ.
Một số điều nên lưu ý về script:
- JavaScript tiếp nhật animation timing theo mili giây, chứ không phải là giây CSS chuẩn (CSS cũng dùng được mili giây, miễn là bạn thêm hậu tố
ms
vào giá trị timing). - Web Animations API chỉ số lần lặp lại là
iterations
, chứ không phải tính chất CSSinteration-count
(khi được xác định riêng lẻ); keyword làInfinity
(I viết hoa) cho số lần lặp, chứ không phảiinfinite
của CSS. - Trong Web Animations API, easing mặc định là
linear
, vậy trong demo của chúng ta, ta định rõease
làm giá trị mặc định cho CSS animation.
Kết quả có chức năng tương tự với CSS animation.
See the Pen Basic Red Ball Anim in Web Animation API by SitePoint (@SitePoint) on CodePen.
Tất nhiên, sao chép CSS animation như thế này trong JavaScript không thực sự tận dụng hết mức sự linh hoạt của ngôn ngữ scripting này; để minh họa rõ hơn, chúng ta sẽ tạo một animation hoàn chỉnh hơn nữa.
Dealing Images
Không lâu trước đây tôi từng nghiên cứu làm một cái animation “tán” một loạt hình ảnh lên trang web, giống như búng bài đang chơi lên bàn vậy. Viết từng animation một cho mỗi card trong CSS truyền thống sẽ mất rất nhiều thời gian, và hầu như đều mang lại cùng một hiệu ứng. Thay vào đó, tôi dùng Web Animation API, đây là công cụ hoàn hảo cho yêu cầu lần này.
Progressive Cards
Chúng ta muốn hình ảnh được hiển thị dù JavaScript hay Web Animations API có hiệu lực hay không. Vậy, chúng ta sẽ bắt đầu bằng cách thêm một loạt hình ảnh vào page:
1 2 3 4 5 6 7 8 | <div class="shuffle expose"> <img src="bridgefog.jpg" alt> <img src="daisyface.jpg" alt> <img src="drowninghand.jpg" alt> <img src="firefigure.jpg" alt> <img src="shellhand.jpg" alt> <img src="waterfeet.jpg" alt> </div> |
Chúng ta để trống giá trị alt
của hình ảnh để giữ code cho sạch sẽ; trong phiên bản production, chúng sẽ được điền description. Những bức ảnh này được chụp bở .tafo., được sử dụng theo Creative Commons Attribution-NoDerivs 2.0 Generic license.
Chúng ta có thể thêm một chút CSS để hiển thị hình ảnh với animation nếu điều kiện no-JavaScript vẫn đúng:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @keyframes reveal { to { opacity: 1; } } .shuffle { min-height: 100vh; position: relative; } .shuffle img { width: 33%; opacity: 0; } .expose img { animation: reveal 1s forwards; } |
Thời gian xuất hiện của từng hình ảnh có thể được delay với:
1 2 3 4 | .expose img:nth-child(1) { animation-delay: 1s; } .expose img:nth-child(2) { animation-delay: 2s; } .expose img:nth-child(3) { animation-delay: 3s; } … |
Thông thường, tan có thể tự động hóa những khai báo này với Sass hoặc một công cụ xử lý khác.
Hình ảnh sẽ có viền nếu element mẹ có class
webanim
, được áp dụng với JavaScript:
1 2 3 | div.webanim img { border: 1.4vw solid #eee; } |
JavaScript
Chúng ta sẽ thêm script vào cuối page. Sẽ cần vài giá trị ngẫu nhiên trong animation, nên tôi sẽ tạo một hàm riêng cho mục đích này:
1 2 3 | function getRandom(min, max) { return Math.random() * (max - min) + min; } |
Sau đó, tôi sẽ thu thập image container và tất cả hình ảnh trong đó, đồng thời set một biến incrementor:
1 2 3 | var imgContainer = document.querySelector(".expose"), imgSeq = document.querySelectorAll(".shuffle img"), i = 1; |
Sau đó, tôi sẽ loại bỏ class expose
(hình ảnh không có animation và có opacity ở mức 0
) vì CSS animation chỉ làm việc nếu container có một class expose
:
1 2 | imgContainer.classList.remove("expose"); imgContainer.classList.add("webanim"); |
Class webanim
sẽ đặt hình ảnh vào khung.
Phần lớn scripts diễn ra ở vòng lặp forEach
trong một hàm. Tôi sẽ call hàm một khi tất cả dữ liệu ảnh đã được tải (trái với node của ảnh chỉ đơn thuần xuất hiện trong DOM) bằng script imagesLoaded
cực hay của David DeSandro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function racknstack() { Array.prototype.forEach.call(imgSeq, function(photo) { setTimeout(function() { photo.style.position = "absolute"; photo.style.width = getRandom(33, 45) + "%"; photo.style.left = getRandom(-5, 65) + "%"; photo.style.top = getRandom(-6, 60) + "vh"; var animate = photo.animate([{ opacity: '0', transform: 'rotate(' + getRandom(-12, 12) + 'deg) scale(1.2)', boxShadow: '0 0 12px 12px rgba(0,0,0,.3)' }, { opacity: '1', transform: 'rotate(' + getRandom(-8, 8) + 'deg)', boxShadow: '0 0 6px 6px rgba(0,0,0,.3)' }], { duration: 2000, fill: 'forwards' }); }, 1800 * i) i++; }) } |
Cũng như trên, trước hết chúng ta phải call hàm:
1 | imagesLoaded(imgSeq, racknstack); |
Kết quả:
See the Pen Random Stacked Images w/ WebAnim API & Progressive JS by SitePoint (@SitePoint) on CodePen.
Theo đó, hàm này:
- đặt hình ảnh vào vị trí tuyệt đối;
- Tạo chiều rộng ngẫu nhiên cho hình ảnh (giá trị phần trăm, để hình ảnh vẫn linh động và responsive)
- Tạo vị trí ngẫu nhiên cho hình ảnh (bao gồm vị trí tiêu cực (negative) có thể xuất hiện, nghĩa là hình ảnh chỉ xuất hiện hơi trật khỏi phần viền của viewport)
- Animate hình ảnh bằng Web Animation API. Keyframe sẽ làm mờ hình ảnh từ
0
đến solid opacity (làm đục đặc), khi xoay ảnh.
Ta cần đến fill forward
vì chúng ta muốn hình ảnh giữ nguyên trạng thái cuối cùng sau khi đã hoàn thành animation.
Tham gia cộng đồng lập trình Web lớn nhất Việt Nam: Vietnamwebsummit.com
Từ điểm mạnh này đến điểm mạnh khác
Web Animation API vẫn đang được phát triển nhanh chóng:
- Bản nháp làm việc được hiện đang được hoàn chỉnh nhanh chóng, với lượng lớn component được Chrome và Firefox hỗ trợ.
- Bạn cò có thể thả polyfill độc lập, để hỗ trợ cho các trình duyệt chưa bổ sung spec này.
Kết luận
Như bạn thấy đấy, Web Animation API cho phép ta chuyển từ tính chất cụ thể, khai báo, từng bước một của CSS animation sang hướng linh hoạt, vô điều kiện của JavaScript, từ đó cho ra các animation expressive (sống động), random (ngẫu nhiên), và hiệu năng cao.
ITZone via sitepoint