Web 高性能动画实践
一起来看看以下一组完成相同的动作的动画视频,它们在一起缓慢地移动。
它们一个以 transform 完成,另一个以其他的方式。 你可以清楚地看到它们的不同,现在我们来看看它们为什么会这样。
通过 DevTools 从文档对象模型到像素级别的观察
当你用 Chrome DevTools 的 timeline 来查看的话,你可以看到跟一下相似的模式:
Chrome DevTools 框架模式。 瀑布流越高,说明浏览器做的越多。
浏览器处理的过程很简单:计算元素的样式(重新计算样式),生成每个元素的几何形状和位置(布局),绘制图层中的每个像素(初始化绘图并且进行绘图)并且将图层绘制到屏幕上(图层的合成)。
为了生成流畅的动画,你需要让浏览器尽可能少地工作,而最好的方式就是指改变影响合成的属性——transform 和 opacity。瀑布流越高 ,浏览器为了计算每个像素,就做的越多。
这个建议是跨浏览器兼容的。 Chrome, Firefox, Safari 和 Opera 都对 transforms 和 opacity进行硬件加速。不幸的是,对于Internet Explorer 10+ 开启硬件加速的条件不是很明确,但值得庆幸的是当F12工具加入到IE11中的时候,这一点将变得清晰。
对布局进行动画
当你改变一个元素的时候,浏览器可能需要计算布局(位置和大小),这将影响到所有被这项改变影响的元素。如果你改变了一个元素,那么其他元素的几何结构可能会需要重新计算。举个例子,如果你改变 <html>
元素的width属性,那么它所有的子元素都将被影响。由于元素的溢出和相互之间的影响,改变可能导致文档树的布局自下而上地被重新计算。
文档树越大,计算布局所花费的时间就越长。所以你应该尽力避免对那些影响布局的的属性设置动画。
你是否用类来存储应用或者元素的状态?当这些元素发生变化的时候,浏览器可能需要重新进行样式的计算和布局。在应用中,要注意那些可能忽略的会引起布局改变的变化;它们可能不会产生动画,当时它们将产生高昂的代价!
项目是一些常见的,会引起布局变化的属性 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: http://goo.gl/lPVJY6
绘图属性的动画
对一个元素的改变也可能引起绘图,而在现代浏览器中,主要的绘制工作会在软件光栅化中进行。这取决于元素在你的应用中如何分层。挨着被改变的元素旁边的其他元素也可能需要被重新绘制。
如果你对布局和图层的概念不了解,推荐你阅读Tom Wiltzius的 introduction to the topic.
有很多属性会引起元素的绘制,但以下这些是最常见的属性:
影响绘制的样式
color | border-style |
visibility | background |
text-decoration | background-image |
background-position | background-repeat |
outline-color | outline |
outline-style | border-radius |
outline-width | box-shadow |
background-size |
Source: http://goo.gl/lPVJY6
如果你在元素中对以上的属性设置动画,那么将会引起重绘,并且元素所属的图层将提交给GPU进行处理。对于移动端设备来说,这代价是非常昂贵的,因为它们的CPU的处理能力明显弱于桌面端。这意味着,任务将用更长的时间来完成;并且CPU和GPU之间的带宽是有限的,所以数据的上传需要花费很长的一段时间。
合成属性的动画
有一个CSS属性,你可能认为它会引起重绘,但有时候并不会。就是:opacity. 当GPU在合成元素的纹理结构的时候,会以一个较低的alpha值去处理opacity的改变。它的条件是,元素必须是图层中唯一的一个元素。如果它和其它的元素组合在一起,那么对opacity的改变也会让GPU(错误地)淡化其它的元素。
在Blink和WebKit内核的浏览器中,对于在 CSS 的transition或者animation中有opacity的改变的元素,将会为其创建一个图层。但也有很多开发者使用translateZ(0)
或者translate3d(0,0,0)
来人为地强制性地创建一个图层。 强制创建一个图层可以确保图层被绘制完毕并且可以在动画开始的时候,马上进入就绪状态。(创建并且绘制一个图层是一个不会有人反对的操作,并且它会延迟你的动画的开始),而且由于反锯齿的改变,动画中将不会出现唐突的变化。然而,需要有节制地增加图层;如果过分的增加图层,那么将会导致闪烁。
在Chrome浏览器中,没有根节点并且透明的图层中,使用 灰度级平滑处理要胜于子像素抗锯齿,这个方法的效果十分显著,特别是当抗锯齿的方法改变得很突然的时候。如果你想为一个元素增加图层,那么请在动画开始之前进行处理。
一个元素的变换,归结为改变它的位置,旋转角度和缩放。通常,位置的改变是使用 left
和 top
属性来改变的。问题是,如前面所述,left
和 top
都会引起图层的变化,并且它的代价是昂贵的。更好的解决方案是使用不会引起图层变化的 translate
属性。
在 Chrome Canary 和 Safari 中,你可以对 filters 设置动画,因为它们是用主进程来处理的所以它们会被加速完成,并且通常都会有良好的表现。但是目前 filters 不被 Internet Explorer 或者 Firefox 支持,所以你需要谨慎地使用它。
命令式动画 vs 说明式动画
开发者常常需要决定它们的动画用 JavaScript(命令式)来完成还是用 CSS(说明式)来完成。它们各有优缺点,让我们一起来看看:
命令式
命令式动画主要的优点同时也是它主要的缺点的是:它在浏览器主进程的JavaScript中运行。主进程已经忙于运行其他的JavaScript,样式的计算,布局还有绘制。所以进程内存在这资源竞争。这实质上增加了掉帧的风险,可能这一帧是你认为最重要的帧。
JavaScript中的动画可以为你提供更多的控制:开始,暂停,回放,中断和取消等细节。有一些特效如parallax 的滚动只能用JavaScript来完成。
声明式
作为替代的方案,你可以用 CSS 来实现你的渐变和动画。最主要的好处就是,浏览器会对动画进行优化。如果有需要,它会创建图层。并且可以在主进程之外完成一些操作。它最主要的缺点就是 CSS 动画相对于 JavaScript 动画而言,缺乏表现力。并且很难有意义地组织动画,这意味着创造动画会带来较高的复杂度和错误率。
展望未来
随着web标准的发展,一些关于动画的限制也将会被解决。来自Google的Ian Vollick有一个提案,就是允许通过工作线程运行JavaScript动画,并且不会引起布局的改变和样式的重新计算。
对于动画规范的制定有兴趣的人,可以看一看Web动画规范说明,它正由Jake Archibald广泛地征集意见。
总结
更好地制作动画,对于优秀的 Web 浏览体验来说是至关重要的。你需要一直注意避免使用那些会引起布局或者绘图改变的属性。他们都会都会产生高昂的代价和引起跳帧。由于浏览器有机会对动画进行优化,所以声明式的动画要优于命令式的动画。
目前,transforms 属性是用来制作动画最好的属性,因为 GPU 可以为繁重的计算工作提供协助。因此,如果你的动画能够限制为只使用下面这些属性来完成,那么就这么干吧。
- opacity
- translate
- rotate
- scale
未来也许会有新的方法来制作动画,它可以让你像 JavaScript 那样子表达但是不消耗主线程的资源;或者可以像 CSS 那样优化但是又没有那么多的限制。但是在那一天到来之前,好好地制作,让你的动画有流畅顺滑的体验。
资源
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论