原文连接: An Introduction to Hardware Acceleration with CSS Animations
过去几年来,咱们常常会据说「硬件加速」及其对页面性能带来的提高——使动画即便在移动设备上也能有更顺滑的表现。不过,我认为有很多开发者并不真正了解「硬件加速」是如何工做的,也不清楚该如何利用它来使咱们的动画更加出彩。css
「硬件加速」听起来是涉及高等数学知识的复杂玩意,在本篇文章中,我将会阐述这一特性,并演示如何在前端项目中应用。前端
让咱们先看一个例子:有多个重叠在一块儿的球(Z轴重叠,看起来就只有1个球),咱们须要用动画移动这些球。最简单的办法是改变 top
和 left
属性。固然你也可使用 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()
方法代替操做 left
和 top
,以解决这个问题: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
太棒了,如今动画变得更加顺滑了!但这是为何呢?这是由于,与 left
和 top
的改变不一样,CSS transforms 不会致使重绘(repaints)。让咱们看一下在动画执行过程当中 Chrome's DevTools 的 Timeline 是什么样的:canvas
在改变 left
和 top
的例子中,咱们能够看见每次动画前的一块绿色条,这表明一次开销昂贵的重绘操做。咱们须要让动画帧率在 60fps 以保证流畅,但这里的帧率显然不足 60fps。浏览器
而后咱们在看一下使用 CSS transforms 以后的 Timeline:ide
能够看到,动画过程当中已经没有绿色条区域了。post
Chrome's DevTools 提供了另外一个「Enable paint flashing」功能能够追踪重绘的过程。你能够打开 Chrome's DevTools,按下 Esc,选择「Rendering」标签(译者注:若是没有此标签的话,点击左侧三个点选择此标签)。当此功能开启后,重绘区域会出现绿色方框。在改变 left
和 top
的动画例子中,整个动画过程当中小球周围都有绿色方框,说明一直都在执行重绘。
而在使用 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 动画里的球都有橙色边框,而且都被分到不一样的合成层中。
在这里你可能会问:「浏览器会在何时建立一个独立的合成层?」
主要存在如下这些状况:
<video>
和 <canvas>
元素读到这里你是否会想:「等一下!这个例子使用的是 2D 的 transform,不是 3D 的。」。没错,这就是为何在咱们的 timeline 中,在动画的首位会有两次额外的重绘操做。
2D 和 3D transform 的区别在于,浏览器对 3D transform 会事先建立一个独立的合成层,而对 2D transform 则是实时地建立合成层。在动画开始的时候,一个新的合成层被建立而且向 GPU 载入纹理,这一操做致使了一次重绘。在随后的过程当中,动画由 GPU 中的合成器完成。当动画结束后,先前被建立的合成层会被移除,这一操做会再致使一次重绘。
并非元素上全部的 CSS 属性变化均可以在 GPU 里处理的,只有如下属性能够被支持:
所以,为了获得流畅、高质量的动画效果,咱们应该尽量使用这些 GPU 友好的 CSS 属性。
在某些状况下,咱们可能须要在动画开始前就让元素在 GPU 中渲染,这么作能够避免动画开始时建立新层的那一次重绘。咱们能够用一个叫「transform hacky」的小技巧来作到这些:
.example1 { transform: translateZ(0); } .example2 { transform: rotateZ(360deg); }
这会让浏览器知道咱们要进行 3D 变化,浏览器会所以建立一个新层并事先将元素移到 GPU 中,以此来触发了硬件加速。
若是由于背后其余元素等致使重绘耗费高昂的状况下,咱们也可使用这个技巧。咱们回到第一个例子,作一些小的调整:如今咱们有一个球以及一个容器,容器的背景图片使用了 CSS filters 进行模糊处理。如今咱们让这个小球使用 left
和 top
进行动画:
咱们发现小球的运动有些卡顿,这是由于每次模糊背景的重绘耗费都十分高昂。
如今咱们在容器上使用 「transform hacky」试试:
效果不错!咱们的动画变得顺滑多了。但这是为何呢?由于咱们如今已经把绘制代价高昂的背景移到了另外一个合成层上,而单独绘制小球的性能消耗就小得多了!
天下可没有免费的午饭,硬件加速也同时会带来如下几点问题:
内存是最严重的问题,要知道在GPU里加载太多的纹理(texture)会致使严重的内存负担。这在移动设备上尤为明显,甚至能够直接引发浏览器崩溃!因此要注意使用硬件加速可能带来内存负担的后果,千万不要在页面上每个元素都使用硬件加速。
因为 GPU 与 CPU 之间不一样的渲染机制,在 GPU 中的渲染会影响字体的抗锯齿。你能够在动画结束后关闭硬件加速,但在动画期间字体的渲染仍是会模糊。关于问题渲染问题的更多解释你能够阅读 Keith Clark 的这篇文章。
使用「transform hacky」这种技巧来建立分离的合成层,这种方法看上去仍是太繁琐了。浏览器为了提供一个更直接的方法,引入了 will-change
属性。这个新特性容许你事先告诉浏览器哪一个属性要发生改变,浏览器即可以对此做出相应的优化。这里给一个 transform
属性即将发生改变的例子:
.example { will-change: transform; }
不过并非全部浏览器都支持 will-change
属性,你也能够经过如下参考资料了解更多 will-change
属性相关的知识:
总结一下咱们提到的内容:
译者注:第一次翻译,不周到之处还请指出,欢迎留言讨论