絲滑順暢:使用CSS3獲取60FPS動畫

原文連接: Smooth as Butter: Achieving 60 FPS Animations with CSS3css

在移動端使用動畫元素是很容易的.html

若是你能遵循咱們的這裏的提示, 在移動端適當的使用動畫元素, 能夠變得更加容易.css3

在這些天裏, 每一個人都不會適當的使用CSS3動畫. 有些最佳的實踐方法, 一直被忽視. 被忽視的主要緣由是人們不能真正的理解,這些實踐存在的真正緣由, 以及爲什麼能獲得大力支持.web

如今設備的規格很是多, 若是你不能經過仔細思考優化你的代碼, 使用順暢的動畫, 你會給大部分的人帶來很差的體驗.chrome

記住: 儘管一些高端, 旗艦設備不斷推進發展, 但這世界上大部分的設備, 和高端機相比差太多, 就像一個帶着LCD的算盤.瀏覽器

咱們會告訴你如何使用CSS3, 發揮它的最大功效. 爲了達成這一點, 咱們首先須要學習一點東西.app

理解時間線(Timeline)

渲染和使用元素的時候, 瀏覽器會作些什麼? 這是很是簡單的一條時間線, 被稱之爲關鍵渲染路徑(Critical Rendering Path)chrome-devtools

圖片

咱們應該專一於改變影響合成步驟的屬性, 而不是增長上一次佈局的壓力.函數

1.樣式

圖片

瀏覽器開始計算樣式, 以便應用到元素上 - 從新計算樣式佈局

2.佈局

圖片

下一個步驟中, 瀏覽器開始計算模型和各個元素的位置 - 佈局. 這是瀏覽器設置在頁面中設置例如屬性的地方, 也包括了外邊距, 或者是實例的左/高/右/下.

3.繪製

圖片

瀏覽器開始將每個元素在像素級別填充到圖層中. 咱們使用的這些屬性包括: box-shadow, border-radius, color, background-color, 等等.

4.合成

這是你須要操做的地方, 由於這是瀏覽器開始在屏幕繪製全部的圖層.

圖片

如今瀏覽器可以產生動畫的有四個屬性, ,最好使用tansform, 和opacity屬性.

  • 位置: transform: translateX(n) translateY(n) translateZ(n);
  • 擴大/縮放: transform: scale(n);
  • 旋轉: transform: rotate(ndeg);
  • 透明: opacity: n;

如何得到每秒60幀的實現

經過上面這些思考, 讓咱們開始幹吧.

咱們經過一個HTML開始. 咱們會建立一個很是簡單的模型, 而後在.layout中放置一個app-menu.

<div class="layout">
  <div class="app-menu"></div>
  <div class="header">
    <div class="menu-icon"></div>
  </div>
</div>

開始使用一個錯誤的方式

Going About It the Wrong Way(下爲原文翻譯)

.app-menu {
  transition: left 300ms linear;
  left: -60%;
}
.open .app-menu {
  left: 0;
}

看到被咱們改變的屬性了嗎? 你應該避免使用transition上面的top/right/bottom/left屬性. 那並不會產生一個流暢的動畫. 由於他們讓瀏覽器一直在建立layouts, 這會影響到他全部的子組件.

他的結果就像這樣:

動圖

這個動畫徹底不暢. 咱們經過使用DevTools Timeline來看看底層發生了什麼, 這是他的結果:

圖片

這清楚的展示了FPS的不規則, 而且性能不好.

"綠條表示FPS. 他上面的條表示動畫在60FPS如何渲染. 下面的條表示低於60FPS. 因此, 理想狀況下, 你但願綠條能在整個時間線保持較高的水平. 紅條也能表示出閃避jank(避開了渲染時間?), 因此, 還能夠經過消除紅條, 來表示你性能的進步." 感謝Kayce Basques指出.

第一部分實踐糾正

關於Dev Tools

官方文檔: Timeline已經再也不使用, 下面是使用Performance

圖片

關於渲染順序

官方文檔:

這是一個疑問句? 如今我發現, 官方文檔上所說的CRP和這篇文章說的不太同樣, 有空翻譯下官方文檔.

使用 Transform

.app-menu {
  transition: transform 300ms linear;
  transform: translateX(-100%);
}
.open .app-menu{
  transform: none;
}

transform屬性做用到Composite合成階段. 這裏告訴咱們, 只要動畫開始, 瀏覽器全部的圖層都渲染完成並準備好了, 因此動畫渲染的時候, 間隔很是小.

圖片

在實際中的時間線中展現:

圖片

如今的結果變得好一些了, FPS可以進行更多渲染, 而後動畫更加流暢.

我的第二部分測試

圖片

和上一次我的測試相比, 省去了layout階段, 就是佈局的時間.

在GPU中運行動畫

那麼, 讓我把他提升一個等級. 爲了保證動畫運行的順暢, 咱們是用GPU開始渲染動畫.

.app-menu {
  transition: transform 300ms linear;
  transform: translateX(-100%);
  /* transform: translate3d(-100%, 0, 0); */
  will-change: transform;
}
.open .app-menu{
  transform: none;
}

儘管一些瀏覽器依舊須要使用translateZ()translate3d()做爲備選方案, will-changeCSS will-change - how to use it, how it works纔是之後的趨勢. 這樣作, 能夠把元素提高到另外一層上, 因此, 瀏覽器不須要考慮佈局的渲染和繪製.

圖片

可以看出他的順暢嗎? 渲染路徑會證明這一點.

圖片

動畫的FPS是很是連續的, 而且動畫的渲染是很是快速的. 可是依舊有一個紅框在渲染的時候. 那只是在開始的時候, 一個小瓶頸.

記住剛開始時建立的HTML的結構. 讓咱們使用JavaScript在結構中控制一個app-menudiv.

function toggleClassMenu() {
  var layout = document.querySelector('.layout')
  if (!layout.classList.contains('app-menu-open')) {
    layout.classList.add('app-menu-open')
  } else {
    layout.classList.remove('app-menu-open')
  }
}

這個問題是: 咱們給layout這個div添加了類名, 讓瀏覽器再一次計算了樣式, 影響了渲染性能.

我的關於第三部分測試

首先是, 使用translate3d的效果:

圖片

這是使用了will-change的效果:

圖片2

一樣, 我也是使用這種控制類名的方式實現的動畫.

60FPS的順暢動畫

若是咱們從視圖層外邊建立一個區域替代以前的作法呢? 一個隔離的區域, 能夠確保影響到的元素, 就是想要進行動畫的.

因此, 咱們使用下面這種HTML結構.

<div class="menu">
  <div class="app-menu"></div>
</div>
<div class="layout">
  <div class="header">
    <div class="menu-icon"></div>
  </div>
  <a href="www.baidu.com">baidu</a>
</div>

如今咱們可使用稍微不一樣的方式控制menu的狀態了. 當動畫結束的時候, 咱們使用JavaScript中的transitionend函數, 刪除還有動畫的類名.

function toggleClassMenu() {
  myMenu.classList.add("menu--animatable");
  if (!myMenu.classList.contains("menu--visible")) {
    myMenu.classList.add("menu--visible");
  } else {
    myMenu.classList.remove('menu--visible');
  }
}

function OnTransitionEnd() {
  myMenu.classList.remove("menu--animatable");
}

var myMenu = document.querySelector(".menu");
var oppMenu = document.querySelector(".menu-icon");
myMenu.addEventListener("transitionend", OnTransitionEnd, false); // 只在動畫期間添加動畫函數
oppMenu.addEventListener("click", toggleClassMenu, false);
myMenu.addEventListener("click", toggleClassMenu, false);

讓咱們所有結合起來, 而後檢查結果.

下面是完整, 可使用CSS3的例子, 每一處都使用了最正確的方式.

body {
    margin: 0;
    padding: 0;
  }

  .menu {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    pointer-events: none; /* 這個屬性表示, 即便是上面有一層, 也不影響, 下面元素的使用 */
    z-index: 150;
  }

  .menu--visible {
    pointer-events: auto; /* 遮蓋了, 也就不讓用了 */
  }

  .app-menu {
    background-color: #fff;
    color: #fff;
    position: relative;
    max-width: 400px;
    width: 90%;
    height: 100%;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
    -webkit-transform: translateX(-103%);
    transform: translateX(-103%);
    display: flex;
    flex-direction: column;
    will-change: transform;
    z-index: 160;
    pointer-events: auto; /* 這是咱們的側邊欄, 打開的時候, 不讓用下面的元素 */
  }

  .menu--visible .app-menu {
    -webkit-transform: none;
    transform: none;
  }

  .menu--animatable .app-menu { /* 消失的時候, 先慢後快 */
    transition: all 130ms ease-in;
  }

  .menu--visible.menu--animatable .app-menu { /* 出現的時候, 先快, 後慢 */
    transition: all 330ms ease-out;
  }

  .menu:after {
    content: '';
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.4);
    opacity: 0;
    will-change: opacity;
    pointer-events: none;
    transition: opacity 0.3s cubic-bezier(0, 0, 0.3, 1);
  }

  .menu--visible.menu:after {
    opacity: 1;
    pointer-events: auto;
  }

  /* aux */

  body {
    margin: 0;
  }

  .layout {
    width: 375px;
    height: 667px;
    background-color: #f5f5f5;
    position: relative;
  }

  .header {
    background-color: #ccc;
  }

  .menu-icon {
    content: "Menu";
    color: #fff;
    background-color: #666;
    width: 40px;
    height: 40px;
  }

  .app-menu {
    width: 300px;
    height: 667px;
    box-shadow: none;
    background-color: #ddd;
  }

  .menu:after {
    width: 375px;
    height: 667px;
  }

圖片

讓咱們看下Timeline展現給咱們的?

圖片

看到了嗎? 很是流暢.

最後一部分的測試

發現, 性能提高主要在Event中, 其餘未能看出提高, 並進行了一個名爲Fire Idle Callback.還須要深刻了解下. 下圖爲實操圖片:

圖片

總結

  • 再次深刻學習了事件流.
  • 沒有搞明白關鍵渲染路徑究竟是什麼. 文章和官網說的不同.
  • 知道了一個will-changepointer-events
相關文章
相關標籤/搜索