【译】CSS动画之硬件加速

原文连接: An Introduction to Hardware Acceleration with CSS Animations

过去几年来,咱们常常会据说「硬件加速」及其对页面性能带来的提高——使动画即便在移动设备上也能有更顺滑的表现。不过,我认为有很多开发者并不真正了解「硬件加速」是如何工做的,也不清楚该如何利用它来使咱们的动画更加出彩。css

「硬件加速」听起来是涉及高等数学知识的复杂玩意,在本篇文章中,我将会阐述这一特性,并演示如何在前端项目中应用。前端

为何要理解

让咱们先看一个例子:有多个重叠在一块儿的球(Z轴重叠,看起来就只有1个球),咱们须要用动画移动这些球。最简单的办法是改变 topleft 属性。固然你也可使用 JavaScript 去作,但这里我会使用 CSS(注意:如下例子我没有使用浏览器前缀,你可使用如 Autoprefixer 来保证浏览器兼容性):git

.ball-running {
  animation: run-around 4s infinite;
}

@keyframes run-around {
  0%: {
    top: 0;
    left: 0;
  }

  25% {
    top: 0;
    left: 200px;
  }

  50% {
    top: 200px;
    left: 200px;
  }

  75% {
    top: 200px;
    left: 0;
  }
}

点击查看DEMO(能够点击按钮经过 JavaScript 来启动动画)。github

点击「Start Animation」后,你会注意到即便在桌面浏览器上,动画看起来也不太顺滑(译者注:若是在你的浏览器上看起来很顺滑,说明你的电脑性能太好了,你能够打开 F12 -> Performance -> CPU,设置 CPU 到 4x slowdown 后再试)。要是你是在移动设备上测试的话,帧率可能会远低于 60fps。咱们可使用 CSS transform 的 translate() 方法代替操做 lefttop,以解决这个问题:web

.ball-running {
  animation: run-around 4s infinite;
}

@keyframes run-around {
  0%: {
    transform: translate(0, 0);
  }

  25% {
    transform: translate(200px, 0);
  }

  50% {
    transform: translate(200px, 200px);
  }

  75% {
    transform: translate(0, 200px);
  }
}

点击查看DEMOchrome

太棒了,如今动画变得更加顺滑了!但这是为何呢?这是由于,与 lefttop 的改变不一样,CSS transforms 不会致使重绘(repaints)。让咱们看一下在动画执行过程当中 Chrome's DevTools 的 Timeline 是什么样的:canvas

DevTools Timeline during the animation with left and top

在改变 lefttop 的例子中,咱们能够看见每次动画前的一块绿色条,这表明一次开销昂贵的重绘操做。咱们须要让动画帧率在 60fps 以保证流畅,但这里的帧率显然不足 60fps。浏览器

而后咱们在看一下使用 CSS transforms 以后的 Timeline:ide

DevTools Timeline during the animation with CSS transform

能够看到,动画过程当中已经没有绿色条区域了。post

Chrome's DevTools 提供了另外一个「Enable paint flashing」功能能够追踪重绘的过程。你能够打开 Chrome's DevTools,按下 Esc,选择「Rendering」标签(译者注:若是没有此标签的话,点击左侧三个点选择此标签)。当此功能开启后,重绘区域会出现绿色方框。在改变 lefttop 的动画例子中,整个动画过程当中小球周围都有绿色方框,说明一直都在执行重绘。

而在使用 CSS transforms 的例子中,重绘的方框只在动画的首帧和末帧出现。

那么 CSS transforms 到底是如何作到动画过程当中不重绘的呢?简单来讲,CSS transforms 是直接发生在利用硬件加速的显存中,从而避开了软件的渲染。咱们一块儿来看一下详细的说明。

硬件加速是如何工做的

当浏览器收到一个 HTML 后,会进行解析并构建 DOM 树。随后,浏览器能够根据 DOM 树和 CSS 构建出渲染树,渲染树是由页面上须要渲染的元素组成的。每一个渲染元素都会被分配给一个图形层,每一个图形层则会被做为一个纹理(texture)提交给 GPU。而这里的秘密在于,图形层有可能会在没有重绘的状况下直接在 GPU 中转变,就好比 3D 图像。这个转变是由一个独立的合成器(Compositor)流程完成的,你能够阅读 the composition in Chrome here 了解更多。

在咱们的例子中,CSS transform 建立了一个能够直接被 GPU 转换的合成层(composite layer),在 Chrome's DevTools 中能够经过勾选「Show layer borders」选项查看合成层,每一个合成层周围都会有个橙色的边框。

咱们在 CSS transforms 动画里的球都有橙色边框,而且都被分到不一样的合成层中。

Show layer borders during the animation with css transform

在这里你可能会问:「浏览器会在何时建立一个独立的合成层?」

主要存在如下这些状况:

  • CSS 的透视或 3D 变形(咱们的例子就是)
  • <video><canvas> 元素
  • 使用 CSS filters 时
  • 当一个元素覆盖在另外一个有合成层的元素之上时(如:z-index)

读到这里你是否会想:「等一下!这个例子使用的是 2D 的 transform,不是 3D 的。」。没错,这就是为何在咱们的 timeline 中,在动画的首位会有两次额外的重绘操做。

2D css transformations initiate repainting before and after animation

2D 和 3D transform 的区别在于,浏览器对 3D transform 会事先建立一个独立的合成层,而对 2D transform 则是实时地建立合成层。在动画开始的时候,一个新的合成层被建立而且向 GPU 载入纹理,这一操做致使了一次重绘。在随后的过程当中,动画由 GPU 中的合成器完成。当动画结束后,先前被建立的合成层会被移除,这一操做会再致使一次重绘。

在 GPU 里渲染元素

并非元素上全部的 CSS 属性变化均可以在 GPU 里处理的,只有如下属性能够被支持:

  • transform
  • opacity
  • filter

所以,为了获得流畅、高质量的动画效果,咱们应该尽量使用这些 GPU 友好的 CSS 属性。

强制元素在 GPU 里渲染

在某些状况下,咱们可能须要在动画开始前就让元素在 GPU 中渲染,这么作能够避免动画开始时建立新层的那一次重绘。咱们能够用一个叫「transform hacky」的小技巧来作到这些:

.example1 {
  transform: translateZ(0);
}

.example2 {
  transform: rotateZ(360deg);
}

这会让浏览器知道咱们要进行 3D 变化,浏览器会所以建立一个新层并事先将元素移到 GPU 中,以此来触发了硬件加速。

若是由于背后其余元素等致使重绘耗费高昂的状况下,咱们也可使用这个技巧。咱们回到第一个例子,作一些小的调整:如今咱们有一个球以及一个容器,容器的背景图片使用了 CSS filters 进行模糊处理。如今咱们让这个小球使用 lefttop 进行动画:

点击查看demo

咱们发现小球的运动有些卡顿,这是由于每次模糊背景的重绘耗费都十分高昂。

如今咱们在容器上使用 「transform hacky」试试:

点击查看demo

效果不错!咱们的动画变得顺滑多了。但这是为何呢?由于咱们如今已经把绘制代价高昂的背景移到了另外一个合成层上,而单独绘制小球的性能消耗就小得多了!

谨慎使用硬件加速

天下可没有免费的午饭,硬件加速也同时会带来如下几点问题:

内存

内存是最严重的问题,要知道在GPU里加载太多的纹理(texture)会致使严重的内存负担。这在移动设备上尤为明显,甚至能够直接引发浏览器崩溃!因此要注意使用硬件加速可能带来内存负担的后果,千万不要在页面上每个元素都使用硬件加速。

字体渲染

因为 GPU 与 CPU 之间不一样的渲染机制,在 GPU 中的渲染会影响字体的抗锯齿。你能够在动画结束后关闭硬件加速,但在动画期间字体的渲染仍是会模糊。关于问题渲染问题的更多解释你能够阅读 Keith Clark 的这篇文章

展望将来

使用「transform hacky」这种技巧来建立分离的合成层,这种方法看上去仍是太繁琐了。浏览器为了提供一个更直接的方法,引入了 will-change 属性。这个新特性容许你事先告诉浏览器哪一个属性要发生改变,浏览器即可以对此做出相应的优化。这里给一个 transform 属性即将发生改变的例子:

.example {
  will-change: transform;
}

不过并非全部浏览器都支持 will-change 属性,你也能够经过如下参考资料了解更多 will-change 属性相关的知识:

总结

总结一下咱们提到的内容:

  • 利用 GPU 能够提高的动画质量
  • GPU 渲染的动画在任何设备上都应是 60fps
  • 使用 GPU 友好的 CSS 属性
  • 理解如何使用「transform hacky」技巧强制让一个元素到 GPU 中渲染

译者注:第一次翻译,不周到之处还请指出,欢迎留言讨论

相关文章
相关标签/搜索