【译】使用 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…

相关文章
相关标签/搜索