Animation làm sao mới tốt?
Jul 12, 2021 frontendcss

Animation làm sao mới tốt?

animation-performance

Lâu lắm không viết cái gì mới mới, nhân dịp mình vưa làm lại bộ mặt cho cái blog nay tranh thủ làm một bài, chứ không thể nào lười mãi được. :sad:

Dạo mình viết lại cái trang này (không biết nên gọi là blog là gì nữa…), tự dưng nổi hứng muốn làm một cái animation ở chân trang. Thế là lọ mọ thế nào vẽ figma một hồi ra một cái hình như bên dưới:

Ý tưởng ban đầu chính là cái đường cong ở bên dưới sẽ chạy ngang cho nó uốn lượn, sau đó cái mặt trăng sẽ lên xuống theo cái đường cong đó… Nhưng mà mình không biết làm thế nào để cái mặt trăng nó lên xuống, thế là lại dẹp ý tưởng đó đi, thay vào con đường sa mạc đầy sỏi đá như phiên bản hiện tại các bạn thấy :smug:

Phiên bản đầu tiên

Dễ dàng, với trình độ hơn 5 năm chỉ code HTML và CSS mình có thể nhanh chóng có phiên bản đầu tiên, chạy ngon lành cành đào, trông nó như sau:

Thế là mình tò te đi khoe các cô, các chú, các bác trong nghề, xong bị chửi “bố thằng điên” vì không work. Mình chợt nhớ ra mấy tay to mình khoe chỉ dùng mỗi Safari, nên mình bật Safari lên xem thì… quả nhiên là lỗi thật :okay:

Thế là phải cặm cụi ngồi sửa.

Lý do chính cái animation không work trên Safari là do mình dùng background-position để làm hiệu ứng animation, và mình viết theo style position và vị trí như thế này:

css
@keyframes bg-forward {
  from {
    background-position-x: left 1440px;
  }
  to {
    background-position-x: left;
  }
}

Đây là lý do webkit nó không hiểu. Cái style này gọi là two value syntax, các trình duyệt chạy nhân chromiumwebkit sẽ không hiểu mình viết cái gì cả. Thay vì thế mình nên viết thế này:

css
@keyframes bg-forward {
  from {
    background-position-x: 1440px;
  }
  to {
    background-position-x: 0;
  }
}

Ok, dễ hiểu mà phải không? Nhưng mà mình quá lười, do đó lúc này mình đã có ý định buông xuôi, sugar you you go, sugar me me go. :doubt: Tuy nhiên lương tâm của một kỹ sư mặt tiền như mình không cho phép điều đó xảy ra. Thế là quyết định bắt tay vào sửa.

Phiên bản thứ hai sử dụng four-value syntax trong trong thuộc tính background-position, nên nó dĩ nhiên là work rồi. Mình còn kỹ đến độ thêm cả prefix -webkit- cho keyframes mặc dù PostCSS đã có autoprefixer :surrender:

css
@-webkit-keyframes bg-forward {
  from {
    background-position: left 1440px top 0;
  }
  to {
    background-position: left 0 top 0;
  }
}

@keyframes bg-forward {
  from {
    background-position: left 1440px top 0;
  }
  to {
    background-position: left 0 top 0;
  }
}

Ờ nhưng mà thành quả cũng không khác gì ở trên, trừ việc nó có chạy được trên Safari và Chrome. :go:

Hành trình giải quyết hiệu năng

Cách đây vài năm, mình nhớ mang máng có đọc được một article về việc performance của web animation, có thể đạt được 60fps. Dĩ nhiên xưa giờ mình có thấy một vài trang web làm animation rất đẹp, chẳng hiểu sao mình làm nhìn đuồi thế. Vậy là ngồi tìm hiểu.

Cơ chế rendering waterfall

Gốc rễ nằm ở đây, đầu tiên bạn phải biết cách browser render graphic hiển thị lên cho bạn xem như thế nào. Sao mà nó hiện ra được hay ra, đâu phải cứ muốn nó chạy ngang là chạy ngang được đâu.

Hồi xưa học ở trường được code mấy cái app cli, ngoài cái màn hình đen chữ trắng ra thì chẳng biết gì. Mãi được học môn Java được code mấy cái app có giao diện, mà cũng dùng thư viện gui có sẵn cả, có biết cụ thể nó hoạt động như nào đâu.

Thế là mình được một phen mày mò xem thế quái nào mà browser nó render ra được mấy cái DOM lên, mà còn có animation ảo ma canada đến thế.

Mặc dù sự phát triển của software rất nhanh và số lượng các browser cũng có thay đổi, tuy nhiên vẫn chỉ tồn tại một số ít browser engine xịn xò còn sống tới bây giờ và cơ chế render của chúng gần như là giống nhau, nên mình sẽ giới thiệu về cơ chế render một trang web trước.

Khi browser render một trang web tĩnh sẽ có 3 bước:

  1. Layout (hay còn gọi là reflow): Trình duyệt tính toán không gian và vị trí của các phần tử trên màn hình. Ví dụ: chiều rộng của parent sẽ ảnh hưởng tới các phần tử con bên trong
  2. Paint: Trình duyệt vẽ lại các phần tử trên màn hình. Mọi thứ trực quan nhất của phần tử sẽ được vẽ ra trong bước này và xếp trên nhiều layer khác nhau
  3. Composite: Hiển thị các layers đã được vẽ theo đúng thứ tự để các phần tử không bị chồng lên nhau

Cả 3 bước trên sẽ lặp lại thành một quy trình, được gọi là Composition.

Vì việc thực hiện quá trình render là tuyến tính nên phải xong bước này mới đến bước khác, do vậy nếu việc một thuộc tính thay đổi liên tục và thực hiện đủ sẽ dẫn tới việc browser render chậm hơn. Do vậy đại đa số animation chỉ đạt được 30 fps.

Tuy nhiên…

Không phải tất cả các thuộc tính trong CSS đều sẽ phải trải qua đầy đủ 3 bước trong quá trình rendering waterfall, một số thuộc tính sẽ yêu cầu chi phí ít hơn các thuộc tính khác. Mình sẽ chia chúng thành 3 nhóm:

Tuy nhiên có một vài thuộc tính, tùy thuộc vào thằng dev cái core của browser đó nó muốn làm cái gì với cái thuộc tính đó. Do vậy để biết chắc chắn, bạn có thể tham khảo trang CSS Triggers để biết thêm thông tin chi tiết nhé.

Vậy nên cách tốt nhất để tăng hiệu năng cho animation, là cố gắng giảm cost cho browser nhiều nhất có thể. Các bạn có thể xem ví dụ sau để hiểu rõ hơn: https://jsfiddle.net/monodyle/c4v6jzf3/embedded/

Dùng Waterfall devtool để xem hiệu năng thế nào nhé.

Sử dụng margin

Như các bạn thấy, FPS trung bình chỉ rơi vào tầm 46. Thấp nhất rơi vào khoảng 17fps. Thử tưởng tượng đánh Dota 2 với cái cấu hình đó chắc ức chế chết chứ không cần team bạn đánh mình. May quá mình dev web.

Hình trên ta thấy rõ hơn, tốn quá nhiều thời gian (13.11ms) cho quá trình vẽ lại lên browser, trong khi chi phí tối đa cho mỗi frame là 16.7ms. Vậy nên fps thấp là phải rồi…

Sử dụng transform

Có vẻ khá hơn rồi nè, trông thanh framerate đồng đều hơn. FPS trung bình rơi vào khoảng 58.93ms. Chỉ cần nhìn độ sóng sánh của FPS trung bình và limit cũng đủ để thấy hầu như quá trình render đều rất mượt mà.

Xem records, chi phí cho mỗi lần Composite chỉ có 0.08ms, quá đã.

Tổng kết

Vậy thôi, mình sửa background-position thành transform, thế là giờ nhìn cái animation nó siêu mượt. :smug: Các bạn có thể quay lại trang chủ để xem thử animation dưới chân trang.

Bài này phần kỹ thuật mình dịch từ bài Animation performance and frame rate và thêm mắm thêm muối từ kiến thức của mình. Hy vọng bài viết này sẽ giúp ích cho các bạn theo một cách nào đó.

Hẹn gặp lại, hứa sắp tới sẽ có thêm một bài blog thú vị về CSS.