原文链接:High Performance Animations,by Paul Lewis & Paul Irish
Photo by Ricardo Rocha on Unplash
在现代浏览器中,可以非常低成本地执行下列四类动画:定位(position)、缩放(scale)、旋转(rotate)和透明度变换(opacity)。如果你是做其他动画类型的话,需要自担风险,因为很有可能达不到 60 帧的流畅画面效果。
四类动画在浏览器中可以非常低成本执行的动画 | |
---|---|
定位 | transform: translate(npx, npx); |
缩放 | transform: scale(n); |
旋转 | transform: rotate(ndeg); |
透明度变换 | opacity: 0...1 |
下面是针对同一个动画,分别使用 top
/left
和 translate()
的对比效果。
我们能明显感觉到右面的动画效果(translate
实现)更加顺滑。
从 DOM 到像素
当你使用 Chrome DevTools Timeline 面板时,会看到类似下面的界面:
浏览器所经历的过程非常简单:计算应用到元素的样式(Recalculate Style)、生成元素的几何形状及定位信息(Layout)、将每个元素的像素填充到 图层 中(Paint Setup and Paint)、在屏幕中绘制图层(Composite Layers)。
为了实现平滑的动画,最好的方式是改变只会影响 组合图层 这一阶段的属性—— transform
和 opacity
。时间轴瀑布的从起点开始,越长,就表示浏览器需要做越多的工作,才能将像素显示到屏幕上。
这个建议基本上是跨浏览器兼容的。Chrome、Firefox、Safari 和 Opera 所有的硬件都可以对
transform
和opacity
变换做加速。不幸的是,目前还不清楚 Internet Explorer 10+ 使用什么标准来确定硬件加速是否合适,但希望当 IE11 中的 F12 工具发布时,这一点会变得更清楚。
修改布局属性(Layout Properties)
当你在修改元素的时候,浏览器可能会重新布局,所有因修改受到影响的元素的几何形状(定位和尺寸)都要再计算一遍。有时,你修改了一个元素,可能也会导致其他元素的重新计算。例如,如果修改了 <html>
元素的 width
,那么它的所有后代元素都会受到影响。由于元素溢出或相互作用的影响,发生在 DOM 树内的修改有时会导致布局计算一直影响到顶部。
可见的元素树越大,执行布局计算所需的时间就越长,因此必须尽量避免使用会触发布局重新计算的属性。
你是否会把应用程序的一些状态存储元素中,或者是元素会使用类名中?当这些元素被修改时,浏览器可能会被迫重新进行样式计算和布局。要注意在程序中会触发布局计算的属性;它们可能并没有产生动画效果,但开销却是很高的!
这里列举了 最为普遍使用的、修改后会影响布局的 CSS 属性列表:
会影响布局的一些样式
width |
height |
---|---|
padding |
margin |
display |
border-width |
border |
top |
position |
font-size |
float |
text-align |
overflow-y |
font-weight |
overflow |
left |
font-family |
line-height |
vertical-align |
right |
clear |
white-space |
bottom |
min-height |
Source: goo.gl/lPVJY6
修改绘制属性(Paint Properties)
改变一个元素也可能触发绘制,现代浏览器中的大多数绘图都是在软件的 rasterizers 中完成的。根据应用中元素是的分组方式,除了修改的元素外,同一个图层里其他元素可能也需要绘制。
如果你对图层还感觉陌生的话,可以阅读 Tom Wiltzius 写的 这篇文章。
会影响绘图的一些样式
color |
border-style |
---|---|
visibility |
background |
text-decoration |
background-image |
background-position |
background-repeat |
outline-color |
outline |
outline-style |
border-raduis |
outline-width |
box-shadow |
background-size |
Source: goo.gl/lPVJY6
_
如果使用上面的任一属性设置了动画,则被修改的元素将会重新绘制,并将它们所属的图层上传到 GPU。在移动设备上,这特别昂贵,因为 CPU 的功能远不如台式机上的强大,这意味着绘画工作需要更长的时间。 而且 CPU 和GPU 之间的带宽(bandwidth)是有限的,因此纹理(texture)上传需要很长时间。
修改组合属性(Composite Properties)
有一个 CSS 属性,你感觉可能会导致绘制,但却不是的:opacity
。GPU 可以在合成期间简单地通过较低的 alpha 值来绘制元素纹理,来处理对不透明度的更改。但是,要使该功能起作用,该元素必须 是图层里唯一的元素。如果还有其他元素一起组合的话,则在 GPU 中对不透明度的更改也会(错误地)使它们褪色。
在 Blink 和 WebKit 浏览器中,会为具有 CSS 过渡或不透明度动画的元素创建新的图层,但是许多开发人员使用translateZ(0)
或 translate3d(0, 0, 0)
手动强制创建图层。
强制创建图层可确保在动画开始时立即绘制图层并准备就绪(创建和绘制图层是一项非常重要的操作,可能会延迟动画的开始),并且没有由于抗锯齿的变化导致外观上的突然变化。不过,应该谨慎地进行使用它,过多的图层可能会导致出现 jank。
改变一个元素的 transform
可以归结为修改位置、旋转或缩放。我们经常会使用 top
和 left
值设置定位动画,问题如上所示,left
和 top
属性的改变都会触发了布局操作,开销高。更好的解决方案是在元素上使用 translate
,因为它不会触发重新布局。
在 Chrome Canary 和 Safari 中,你也可以设置滤镜动画效果,这些滤镜是在主线程中处理的,通常会被加速处理表现得也很好。但是,由于 Internet Explorer 或 Firefox 中还不支持,大家要谨慎使用。
命令式动画和声明式动画
开发者有时会犹豫这个动画效果是用 JavaScript(命令式)实现好呢,还是用 CSS(声明式)实现呢?它们都有各自的优缺点,我们来看一下:
命令式
命令式动画的主要优点也恰恰是它的主要缺点:JavaScript 是在浏览器的主线程上运行的。主线程已经在忙于其他JavaScript、样式计算、布局和绘制了。因此,通常会存在线程争用。这大大增加了丢失动画帧的机会,这是我们最不想要的。
使用 JavaScript 执行动画确实可以为我们提供很多控制:开始、暂停、反转、中断和取消都很简单。某些效果,像 视差滚动,也只能用 JavaScript 实现。
声明式
另一种方法是用 CSS 写过渡和动画效果,优点是浏览器可以对此优化。必要的时候,还会创建新的图层,并在主线程之外执行操作,这是一个好处。但很多 CSS 动画缺点是缺乏 JavaScript 动画的表达能力,很难用有意义的方式组合动画,写出的动画也很复杂、容易出错。
展望未来
随着 Web 标准的发展,围绕动画的一些限制将会消失。谷歌的 Ian Vollick 提出了一项建议,研究 允许通过 web workers 来实现动画 的想法,而且动画本身不会触发布局或样式的重新计算。
对于声明式的动画方法感兴趣的人,可以看看 Web 动画规范,Jake Archibald 已经详细介绍过了。
总结
好的动画是一个 Web 体验的核心。你应该尽量避免使用会触发布局或绘制的动画属性,这两者开销挺大的,可能导致跳帧(skipped frames)的发生。声明式动画比命令式动画更好,因为浏览器可以提前做优化。
如今,transform
是用来制作动画的最佳属性,因为 GPU 可以辅助这项繁重的工作。因此,可以将动画的限制在以下几种类型。
opacity
translate
rotate
scale
在未来,可能会有新的动画方式,让你尽可能像使用 JavaScript 那样的表达,不用主线程成本;或可以没有限制的优化 CSS 动画,但在这之前,请收下这里介绍的,能让你收获顺滑动画体验的技巧吧。
其他资源
- The current status of animated properties on the web.
- Paul Irish’s deck on performance tooling.
- Antialiasing 101
- We’re Gonna Need A Bigger API!
(正文完)
广告时间(长期有效)
我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。
(完)