【譯】使用 will-change 目標讓瀏覽器擁有 60fps 如絲般流暢的 css 動畫

你們好、這裏就是用了大量動畫去作我的主頁、還有射擊遊戲的 yukijavascript

感受 css 的動畫用的太多了,個人 MacBook 都要「罷工」了,回過頭來,我就給你們總結一下,個人實驗結果吧。(仍是好想用 css 動畫css

此次的例子

這篇文章的目的大概就是,在網頁裏用 css 動畫加一點複雜的動畫,而後去作一些遊戲或者藝術表現的時候,所有都用上 css 的 will-change 屬性,怎麼樣才能讓 GPU 渲染最合適。html

若是你要問 will-change 是什麼?那能夠看一下我放在這篇文章裏的參考列表。vue

示例

css-anime.firebaseapp.com/java

本次要驗證的動畫有下面幾個點:css3

  • 用 css 畫 1000 朵花❀,一朵一朵的作開畫的動畫
  • 每朵花都是 div 元素(此次不用 svg)
  • 每次間隔 32ms 畫一朵
  • 開完以後的花,就保持停留在畫面上
  • 而且畫面一直會在旋轉

不知道你們有沒有遇到過,須要一邊作動畫,還得一直加標籤,而後還得一直顯示,且不能從畫面消失,這種很痛苦的狀況。git

實驗環境以下:github

  • MacBookPro 2017Late / 8GB RAM(常常被叫作「梅」的最低配置)
  • MacOSX Mojave
  • Chrome 74

以後會提一下 Safari 下的狀況。 由於不一樣的環境會有很大的差別,瀏覽器和操做系統不同的話,結果會有差別,我先在這裏事先說明一下。web

站在巨人的肩膀:我以爲值得一讀的文章

譯者注:下面連接所有都是*日文*的,標題我翻譯了一下,日文 ok 的同窗能夠點點看... 瀏覽器

這篇文章參考如下連接:

實驗0:基本元素與動畫的構建

爲了下手方便,我就先隨便的寫了一下,我把代碼貼也上來了,你們看到代碼應該也就就明白了。

花❀的話 html 部分大概就是這樣的,爲了輕鬆和我的興趣,這裏用了下 Vue,其實什麼框架都是能夠的。

Flower.vue

<div class="flower-root" :class="{animate: visible}" @animationend="onEndAnim">
  <!-- 花瓣 -->
  <div class="petal" v-for="(petal, index) in petals" :key="index" :style="{ transform: `rotate(${petal.r}deg)`, 'background-color': petal.col }">
  </div>
  <!-- 中間 -->
  <div class="center"></div>
</div>
複製代碼

花瓣是用 div 搭的,除了角度和顏色是在 template 裏面寫的,其它的都是在下面 <style> 標籤裏寫的。開花的動畫也在這裏寫了(也就是說,動畫跟 Vue 和 javascirpt 沒有關係,就只是用 css 寫的而已)。

Flower.vue

<style lang="scss" scoped>
  .flower-root {
    position: absolute;
    transform: rotate(0deg) scale(0);
    animation: rotate 2s ease-out 0s 1 normal forwards;
  }
  .petal {
    position: absolute;
    width: 70px;
    height: 20px;
    top: -10px;
    left: 0;
    transform-origin: left center;
    border-radius: 50px;
    background-color: #ffb7aa;
  }
  .center {
    position: absolute;
    width: 30px;
    height: 30px;
    left: -15px;
    top: -15px;
    border-radius: 30px;
    background-color: #ffe683;
  }
  // 一邊旋轉一遍變大
  @keyframes rotate {
    0% { transform: rotate(0deg) scale(0); }
    100% { transform: rotate(360deg) scale(1); }
  }
</style>
複製代碼

寫出來的花大概就是這種感受。

示例

讓這個 Flower 組件每隔必定時間,就往畫面里加,而後畫面的總體讓它轉起來。

實驗1:直接跑起來(沒有寫 will-change)

先試試不用 will-change,跑一下動畫,打開 Chrome 的 performance monitor 面板。

示例

譯者注:能夠 f12 -> ctrl + shift + p 輸入 rendering 打開 fps 面板,圖片上左側的 gpu 面板是 mac 系統自帶的。

CPU 還挺給力的,還保持着 60 fps,哦喲,好像還不錯喲。

示例

500 個了。差很少 400 個左右的時候 CPU 已經到達瓶頸了,幀數一會兒就掉下來了,瀏覽器上了 GPU,可是幀數好像仍是很不穩定。

示例

最後,平均幀數到了差很少 20fps,風扇一直轉的大聲...

示例

全部的花都開完了,只剩下 1000 朵靜止的花在旋轉。到這一步,終於回覆了 60fps。

實驗 1 結論

  • css 動畫會由於元素的比例上升,CPU 負荷會上漲。
  • CPU 到達瓶頸的時候,幀數會暴跌。

實驗2:所有都用上 will-change

可是不試試看怎麼會知道呢?變動的地方就是在 style 里加上 will-change: transform。

Flower.vue

.flower-root {
  position: absolute;
  transform: rotate(0deg) scale(0);
  animation: rotate 2s ease-out 0s 1 normal forwards;
  will-change: transform; // 追加
}
複製代碼

Let's start!

示例

第 100 個,很是的流暢,CPU 負荷也很低。

示例

500 個,開始有點掙扎了,GPU 就像吃了芥末同樣,一會兒就竄起來了,CPU 也有點負荷了。

示例

快到結尾了,CPU 和 GPU 都在瘋狂的「慘叫」,performance 也是很危險的狀態。

示例

全部的開花的動畫都結束了以後,負荷也不會降下來, 寫了 will-change 的話,瀏覽器爲了保證接下來的隨時會開始的動畫的性能,就算動畫結束了,負荷也不會降低。

實驗 2 結論

  • 寫了 will-change 的話,就會啓動 GPU 加速
  • 帶 will-change 的元素過多的話,GPU 和 CPU 都會加劇負擔
  • 寫了 will-change 的元素,就算動畫結束了,仍然不會減輕負擔

實驗3:動畫結束後,刪除 will-change 樣式

譯者注:把 will-change 屬性刪除是指把 will-change 的指設置成默認的 auto,以後的翻譯也都是如此。

實驗 2 的問題是,開花的動畫明明都已經結束了,可是 will-change 卻存在着。所以,在實驗 3,開花的動畫結束後,就把 will-change 樣式刪除。

爲了可以動態的設置 will-change 屬性,因此把這個屬性挪到 template 裏了,而後加個變量去控制它(在 Vue 加了個 isMoving 變量)。監聽 animationend 事件,在這個事件內,去改 isMoving 變量。

Flower.vue

<div class="flower-root" :style="{ 'will-change': isMoving ? 'transform' : 'auto', }" @animationend="onEndAnim">
    ...
複製代碼

Flower.vue

private onEndAnim() {
  this.isMoving = false
}
複製代碼

檢測到動畫結束,只把 will-change 的值刪除。

那麼,start!

示例

第 100 個,感受不錯...

示例

500 個,嗯?CPU 好吃力啊。幀數也差很少 300 個左右的時候就降下來了。由於動畫結束了就把 will-change 給刪了,GPU 負荷是輕了,可是 CPU 負擔卻重了。

示例

到最後,差很少要「罷工」了。

示例

全部的動畫結束了以後,終於回覆到 60fps 了。

到底發生了什麼?

好奇怪,結果並無那麼明朗。 我只是在動畫結束後立刻把 will-change 的值刪了而已,到底發生了什麼?

示例

看了下 Chrome 的 performance 的狀況,Update Layer Tree 很迷,隔一段時間執行一次。

這是 Chrome 內部的處理,我也不太知道具體是爲何...

看 DevTools 的 Timeline 面板、理解瀏覽器的渲染機制(日文連接)

Update Layer Tree
GPU 更新着的執行處理的 layer

就是說,GPU 爲了執行處理,把須要處理的元素放到 layer 上,把不須要處理的元素從 layer 刪掉,而後從新構建。

此次試錯的結果,如今的這個狀況,Chrome 會有如下會傾向的點:

  • 依賴 layer 的元素,會使 Update Layer Tree 變重。
  • 比起升級到 layer,從 layer 降級更加耗費性能,也就是說刪除 will-change 的工做很費性能。

我沒有看源碼,這些也只是個人猜想。

實驗 3 結論

  • 動畫結束了以後把 will-change 屬性刪掉,會影響 GPU 的負荷
  • GPU 負荷高的時候,再設置 will-change 的值會很吃力,特別是把 will-change 的指刪掉
  • 高負荷狀態下,動畫結束了,一個一個的去刪 will-change 的值,無疑是致命的

實驗 4:某種程度下,刪除 will-change 的值

在實驗 3 中,動畫結束,一個一個的去刪 will-change 的值,效果會很是的糟糕。不知道爲何 Chrome 要在這個點上這麼的「努力」啊,我試試看有沒有其它辦法繞一下。

既然執一次一次去刪 will-change 的話,會讓 Update Layer Tree 產生負擔,那麼 100 個的話也是同樣的吧。那麼在攢必定數量以後一口氣的去刪掉的話會是怎麼樣呢?立刻來試試看。

準備好 Flower 隊列

Flower.vue

const queueLimit = 100
const stopedFlowers: Flower[] = []
複製代碼

動畫結束後,加到隊列裏, 到 100 個了以後,設置 isMoving 的值,一口氣刪掉 will-change 的值。

Flower.vue

private onEndAnim() {
  stopedFlowers.push(this)
  if (stopedFlowers.length === queueLimit) {
    stopedFlowers.forEach(fl => fl.isMoving = false)
    stopedFlowers.length = 0
  }
}
複製代碼

感受有點像是在投機取巧,哈哈。實驗開始!

示例

第 100 個,這個時候動畫纔開始,will-change 所有都是帶着的,也就是說,如今的這個狀態跟實驗 2 是同樣的。

示例

第 500 個,以 100 個爲單位,刪除 will-change,因此會隔一段時間,幀數會掉一下。

示例

到最後這種狀況會一直持續,雖然會有那麼一瞬間幀數會卡一下,單總的來講,負荷仍是很平衡的。

示例

安全跑完,最後 100 個結束了以後,全部的元素都會刪掉 will-change 的值。這時候,跟實驗 1 和實驗 3 的狀態是同樣的。

實驗 4 結論

  • 若是 will-change 在動畫結束後,攢到必定程度,再去刪除的話,效果會比較好
  • 刪掉 will-change 屬性的那一瞬間,負荷會變重是沒辦法避免的(限本次實驗範圍)。
  • 在大幅度去使用動畫的時候,看準時間去刪掉 will-change,我認爲是有必要的

最後順便測一下 safari 下的狀況吧

在 MacOS/iOS 的 Safari 跑了下,實驗 3 的結果很是的流暢。

示例

也就是說,若是不是 Chrome 的 bug 的話,難道是我使用的問題?由於沒有看源碼,因此也不太肯定。若是有知道的同窗,歡迎在評論區評論。

總結

  • 想流暢的使用 GPU 作 CSS 動畫的話,加上 will-change 屬性吧。
  • will-change 在動畫結束的時候,刪掉這個屬性吧。
  • 在 Chrome 下,will-change 刪掉的話,Update Layer Tree 會從新構建 layer,負擔會變重。能夠每隔一段時間去刪掉 will-change,能夠把影響降到最低。
  • 多確認下不一樣的瀏覽器和不一樣的環境。不要老是想着「Chrome 便是正義」。
  • 奪取確認性能監測面板,還有系統的性能監測面板。就算有 60fps,說不定你的機器正在「慘叫」...

譯者記

以前只是知道 will-change 會讓瀏覽器啓用 gpu 渲染,沒想到這個使用多了,不必定就會爽了,使用 will-change 也是有不少須要注意的點。若是在須要大量使用動畫的狀況下,能夠參考下這篇文章的作法,適當的去刪掉 will-change 屬性。

原文地址:qiita.com/yuneco/item…

相關文章
相關標籤/搜索