您希望您的网络应用程序在进行动画,转场和其他小型UI效果时感觉反应灵敏。确保这些效果是无缝的可以意味着“native”的感觉或笨重,unpolished 的感觉之间的差异。
这是一系列关于浏览器中渲染性能优化的文章中的第一篇。为了解决这个问题,我们将介绍为什么流畅的动画难以实现,需要做什么来实现,以及一些简单的最佳实践。许多这些想法最初是在今年的Nat Duca和Google I / O谈话(视频,幻灯片)上的“Jank Busters”中提出的。
介绍V-sync电脑游戏玩家可能熟悉这个术语,但在网上很少见:什么是v-sync?
考虑一下你的手机显示屏:它每隔60秒刷新一次(通常不是每次)。垂直同步(或垂直同步)是指仅在屏幕刷新之间生成新帧的做法。您可能会认为这就像在将数据写入屏幕缓冲区的过程和读取该数据以将其放到显示屏上的操作系统之间的竞争条件一样。我们希望缓冲帧内容在这些刷新之间改变,而不是在这些刷新期间。否则显示器将显示一帧的一半,另一半显示,导致“ 撕裂 ”。
为了获得流畅的动画,每次刷新屏幕时都需要准备一个新框架。这有两个重要的含义:帧定时(也就是当帧需要准备好)和帧预算(也就是浏览器产生帧的时间)。在屏幕刷新之间只有一段时间才能完成一帧(在60Hz屏幕上~16ms),并且只要最后一帧放在屏幕上就要开始制作下一帧。
时序就是一切(Timing is Everything):requestAnimationFrame许多Web开发人员使用setInterval或setTimeout每16毫秒来创建动画。由于各种原因,这是一个问题(我们将在一分钟内讨论更多),但是特别值得关注的是:
来自JavaScript的计时器分辨率只有几毫秒的量级
不同的设备有不同的刷新率
回想一下上面提到的帧定时问题:在下一个屏幕刷新发生之前,您需要一个完整的动画框架,完成任何JavaScript,DOM操作,布局,绘画等。低定时器分辨率可能使得在下一个屏幕刷新之前完成动画帧的完成变得困难,但是屏幕刷新速率的变化使得固定定时器不可能实现。无论定时器的时间间隔是多少,你都会慢慢地从一个帧的时间窗口中移出,最后丢掉一个。即使定时器以毫秒级精度触发,也不会发生这种情况(正如开发人员发现的那样)) - 定时器分辨率取决于机器是否处于电池插入状态,可能受到后台标签占用资源等因素的影响。即使这种情况很少发生(比如每隔16帧,因为你关了一毫秒)会注意到:你会每秒下降几帧。你也将做这个工作来生成从不显示的帧,这浪费了你在应用程序中花费的时间和精力。
这个小utility(由纳卡杜卡)说明了这一点:
不同的显示器有不同的刷新率:60Hz是常见的,但有些手机是59Hz,一些笔记本电脑在低功耗模式下降到50Hz,一些台式显示器是70Hz。
在讨论渲染性能时,我们倾向于关注每秒帧数(FPS),但差异可能是一个更大的问题。我们的眼睛注意到动画中微小的,不规则的连结,这是一个时间不佳的动画可能产生的。
获得正确的定时动画帧的方法是requestAnimationFrame。当你使用这个API时,你要求浏览器提供一个动画框架。当浏览器即将产生一个新的框架时,你的回调被调用。无论刷新率如何,都会发生这种情况。
requestAnimationFrame 还有其他不错的属性:
后台标签中的动画会暂停,节省系统资源和电池寿命。
如果系统无法在屏幕的刷新率下处理渲染,则可以节制动画并且不太频繁地产生回调(例如,在60Hz屏幕上每秒30次)。虽然这降低了一半的帧率,但它保持了动画的一致性 - 如上所述,我们的眼睛比帧率更适合于变化。稳定的30赫兹看起来比60赫兹更好,每秒丢失几帧。
requestAnimationFrame已经到处都有讨论过了,所以请参考这个来自创意JS的文章来获取更多的信息,但这是平滑动画的重要的第一步。
帧预算Frame Budget由于我们希望在每个屏幕刷新上都准备好一个新框架,因此只有在刷新之间才能完成所有工作来创建新框架。在60Hz的显示器上,这意味着我们已经有了16ms左右的时间来运行所有的JavaScript,执行布局,绘制,以及浏览器必须做的任何事情来获取帧。这意味着如果requestAnimationFrame回调中的JavaScript 花费超过16ms的时间运行,那么您就没有希望为v-sync生成一个帧!
16ms不是很多时间。幸运的是,Chrome的开发者工具可以帮助您追踪在requestAnimationFrame回调期间是否正在消费预算。
考虑一下这个例子,从Paul Irish 关于使用Dev Tools找到动画中的性能问题的优秀视频进行了修改。
打开开发工具的时间表,并快速记录这个动画,这表明我们在动画制作方面超出了预算。在时间线切换到“框架”,并看看:
那些requestAnimationFrame(rAF)回调花费> 200ms。这是一个数量级太长,每隔16毫秒打一个帧!打开其中一个长时间的回调显示里面发生了什么:在这种情况下,大量的布局。
保罗的视频更详细地介绍了重新布局的具体原因(正在阅读scrollTop)以及如何避免。但是,这里的重点是你可以潜入回调中,调查需要花费的时间。
一旦我们解决这个例子,框架看起来不错和短:
注意16ms帧时间。帧中的空白空间是你需要做更多工作的空间(或者让浏览器在后台执行工作)。那个空白是一件好事。
Jank的其他来源试图运行基于JavaScript的动画时遇到麻烦的最大原因是其他的东西可能阻碍你的rAF回调,甚至阻止它运行。即使你的rAF回调是精简的,只需要几毫秒的时间,其他的活动(比如处理一个刚进来的XHR,运行输入事件处理程序,或者在定时器上运行定时更新)都可以突然进入并运行时间没有屈服。在移动设备上,有时处理这些事件可能需要几百毫秒,在此期间,您的动画将被完全停顿。我们把这些动画连接称为“连接”。
没有什么灵丹妙药可以避免这种情况发生,但有一些架构最佳实践可以让自己成功:
不要在输入处理程序中做很多处理!做很多JS或试图在例如onscroll处理程序期间重新排列整个页面是导致可怕笨拙的一个常见原因。
将尽可能多的处理(阅读:需要很长时间才能运行的任何内容)尽可能地推送到您的rAF回调或Web Workers中。
如果你将工作推入到rAF回调中,尝试将其分块处理,这样每帧只处理一点点,或者延迟一段时间,直到重要的动画结束为止 - 这样,您可以继续运行短的rAF回调并平滑地进行动画。
有关如何将处理推送到requestAnimationFrame回调函数而不是输入处理函数的很好的教程,请参阅Paul Lewis的文章Leaner,Meaner,使用requestAnimationFrame的快速动画。
CSS动画在你的事件和RAF回调中,什么比轻量级JS更好?没有JS。
之前我们说过,没有银弹可以避免中断你的rAF回调,但是你可以使用CSS动画来完全避免它们的需要。特别是在Android版Chrome浏览器(以及其他浏览器正在开发类似的功能)中,CSS动画具有非常理想的属性,即使JavaScript运行,浏览器也可以运行它们。
在上面的小节中有一个隐含的声明:浏览器一次只能做一件事。这并不完全正确,但是在任何时候,浏览器都可以运行JS,执行布局或绘画,但一次只能有一个,这是一个很好的假设。这可以在开发工具的时间线视图中验证。此规则的一个例外是Android版Chrome浏览器上的CSS动画(尽管目前还不支持桌面版Chrome)。
如果可能的话,使用CSS动画既简化了应用程序,又让动画顺利运行,即使在JavaScript运行时也是如此。
例如,考虑由rAF驱动的旋转Chrome徽标的示例:
//参见http:// http://paulirish.com/2011/requ estanimationframe-for-smart-animating /有关RAF
polyfills的信息 rAF = window.requestAnimationFrame; var degrees = 0; function update(timestamp){
document.querySelector('#foo')。style.webkitTransform =“rotate(”+ degrees +“deg)”;
console.log('更新到度'+度);
度数=度+1;
RAF(更新); }
rAF(更新);
If you click the button JavaScript runs for 180ms, causing jank. But if instead we drive that animation with CSS animations the jank no longer occurs.
(请记住,在撰写本文时,CSS动画在Android版Chrome浏览器中仅可免费使用,而不是桌面版Chrome。
/ *工具像Modernizr(http:// http://modernizr.com/)可以帮助CSS 填充 * / #foo { + animation-duration:3s; +动画定时功能:线性; + animation-animation-iteration-count:infinite; + animation-animation-name:rotate; } @ +关键帧:旋转; {from { + transform:rotate(0deg); }
to { + transform:rotate(360deg); }}
For more information on using CSS Animations, see articles like this one on MDN.
包起来
它的缺点是:
制作动画时,为每个屏幕刷新帧都很重要。Vsync的动画对应用程序的感觉有着巨大的积极影响。
在Chrome和其他现代浏览器中获取vsync'd动画的最佳方式是使用CSS动画。当您需要比CSS动画提供更多的灵活性时,最好的技术是基于requestAnimationFrame的动画。
要保持RAF动画的健康和快乐,请确保其他事件处理程序不妨碍您的RAF回调运行,并保持rAF回调短(<15毫秒)。
最后,vsync'd动画不仅适用于简单的UI动画 - 它适用于Canvas2D动画,WebGL动画,甚至在静态页面上滚动。在本系列的下一篇文章中,我们将深入讨论这些概念的滚动性能。
参考
http://jankfree.com为Jank Busters I / O谈话,幻灯片和其他信息
使用requestAnimationFrame进行拖动事件http://blog.digitalbackcountry.com/2012/05/using-requestanimationframe-to-optimize-dragging-events/