拥有更好性能的requesAnimationFrame(Better Performance with requestAnimationFrame)

介绍:javascript

  这篇文章讨论的是你能够(也应该)学习经过使用requestAnimationFrame API,而不是使用以前的setInterval/setTimeout方法,来提升动画的性能;如何使用requestAnimationFrame。固然,咱们将会为你展现完善的代码example of requestAnimationFrame in action.html

  requestAnimationFrame 已经被如今因此的主流浏览器支持了,尽管有一些浏览器须要加前缀。Erik Moller已经写了一个能够在全部浏览器均支持requestAnimationFrame的polyfill。 咱们等会会更细致的讨论的。让咱们从头开始讲吧。。。java

 不太好的老方法

  为了能展现出requestAnimationFrame是多么好,咱们首先必须先看看咱们用来作动画的不太好的老方法。我敢确定我不须要告诉你早在Mozilla在实行第一次mozRequestAnimationFrame实验时,你可能已经用setTimeout和setInterval建立动画。我假定你已经熟悉这两个方法了,因此我就不深刻讲解了, 若是你想深刻了解的话,这里有篇John Resig深度讲解的文章:how JavaScript timers workweb

  我不想对这些老方法不屑一顾,不幸的是他们确实有一些缺点。 首先,在切换到一个不一样的标签时,甚至当相应页面最小化时,JavaScript计时器仍将继续工做。形成的后果就是,浏览器继续运行无形的动画,这致使了动画过分绘制,浪费 CPU 周期以及消耗额外的电能等问题。 在移动设备上,这会是特别糟糕的问题。浏览器

  第二,计时器不只会继续运行看不见的动画,并且当时间到了的时候他们老是还要排队等待他们的回调函数。让我来解释下为何这种状况有时会带来问题 -- 就是说你没有很好的完成你的工做,由于某些缘由回调函数完成所须要的时间比你设置计时器的时间要长。一旦计时器的时间到了,他们又将排队到另一个回调函数。尽管前一个尚未完成运行。随着时间的推移,这个过程一直重复着,你能够迅速排队到几乎无数的计时器, 这将致使浏览器中止运行。 图片1具体说明了这个问题。网络

Figure 1: If your callback functions take longer than your timers, enqueuing of multiple callback functions can choke up the browser

图片1:若是回调函数执行的时间比定时器还要长,大量排队的回调函数将会阻塞浏览器dom

  可是即便你的回调函数花费的时间不超过计时器,在这种状况下,setTimeout和setInterval仍然不是最佳的选择。他们都只能以固定的速率从新绘制动画,所以为了确保动画平滑,咱们每每宁肯谨慎来选择一个比显示刷新略高的频率。然而,因为一些帧在显示刷新率准备绘制动画以前被绘制了,所以就被丢弃,这就致使了过分绘制的问题。图片2具体说明了这个问题.函数

Figure 2: Skipped frames can lead to higher CPU usage and battery consumption, and sometimes even choppy animations

图片2:跳帧能够致使更高的CPU使用率和电池消耗,甚至有时不稳定的动画。性能

  当这些方法用于实现循环动画,这些缺点更加危险。在这样的场景中,例如在游戏中或者像个人潮人狗的疯狂的实验中,循环动画致使无休止的的排队等待新的回调函数。若是你想更多的了解网络循环动画的历史。当使用setTimeout和setInterval时,循环动画的行为是什么,以及requestAnimationFrame是怎样改变咱们的代码的。我推荐你阅读Nicholas Zakas写的 Better JavaScript animations with requestAnimationFrame,他在这面讨论的很深刻。学习

 介绍requestAnimationFrame

  requestAnimationFrame正是你所指望的一款API:它将调度动画绘制的职责直接传递给浏览器。浏览器能够作的更好,由于,它知道浏览器的机制是什么!

  requestAnimationFrame是W3CTiming control for script-based animations API的一部分。

  requestAnimationFrame是作什么的

  浏览器很是了解例如tab和窗口状态,网页的哪部分是可见的或者不可见,浏览器什么时候准备绘制,以及哪些其余动画也在运行还有哪些动画是可见的。咱们以前讨论过经过让浏览器控制动画,容许浏览器使用信息来优化动画调度,requestAnimationFrame使用JavaScript定时器追踪问题。所以,requestAnimationFrame的工做流程以下:

  •  首先,RAF(Request Animation Frame)只绘制哪些对用户可见的动画。这意味着RAF会暂停那些隐藏的tabs、最小化的窗口或者隐藏的部分页面的动画,直到这些窗口或页面可见,这样就不会浪费CPU和电池的寿命。
  • 其次,帧只会在当浏览器准备绘制而且没有其余的帧等待被绘制时才会绘制。这意味着这些状况是不可能发生的:使用requestAnimationFrame绘制动画须要排队超过一个回调函数或者使浏览器瘫痪。
  • 第三,帧只会在当浏览器准备绘制而且没有其余的帧等待被绘制时才会绘制,没有没必要要的帧绘制。因此动画更流畅了,CPU和电池的使用进一步获得优化。

  我刚刚只是说没有额外的回调函数队列,直到当前动画完成绘制而且已经渲染了。然而,若是每次回调不止一次的调用requestAnimationFrame()将有一个回调队列,因此将会有额外的回调函数。

  同时,浏览器在一个回流和重绘周期能够有几个动画发生在同一个页面。

 requestAnimationFrame不作什么

  requestAnimationFrame不作:

  •  设置连续的动画; RAF只会安排一个更新,这个更新经过一个id number识别,返回特定的请求。若是须要后续动画帧,requestAnimationFrame将在回调函数再次被调用。若是须要中止动画,你可使用cancelAnimationFrame(id).
  • 确保当RAF 绘制时,只有在须要时才会绘制。
  • 确保动画的同步性。例如,你同时开始两个动画,可是其中一个动画在可见区域,另外一个不在,可见的动画会一直绘制而另外一个则不会;当不可见的再次可见时,他们有可能不一样步了。若是你须要确保他们的同步性,当写代码时,就须要注意了。能够经过确保全部须要同步的动画的状态是由一个不受可见性影响的参数决定的(例如,像动画组的起始时间。)这与依据每一个动画的前一个帧相反。
  • 绘制直到回调函数执行完成。即便你试图触发一个回流中间回调,在正常状况下,经过任何可以触发一个回流方法、重绘。例如,像getComputedStyle().

 怎样使用requestAnimationFrame

  RAF如今被全部现代浏览器所支持,可是有些浏览器须要加前缀。截止写这篇文章为止,加前缀或不加前缀的状况以下:

  •  Opera: 不加前缀 opera15+
  • Chrome:版本24以后不加前缀
  • Safari:加前缀
  • Firefox: 加前缀,尽管版本23以后不加前缀
  • IE:IE10以后不加前缀

  然而,为了使你的代码在全部环节下都能兼容,你应该使用Erik Moller’s polyfill, 它提供了强大的跨浏览器支持,在Paul Irish’s fantastic original groundwork on the subject的基础上进行优化的。

  下面就是咱们青蛙动画演示的简单代码:

var requestId = 0;
var animationStartTime = 0;

function animate(time) {
    var frog = document.getElementById("animated");
    frog.style.left = (50 + (time - animationStartTime)/10 % 300) + "px";
    frog.style.top = (185 - 10 * ((time - animationStartTime)/100 % 10) + ((time - animationStartTime)/100 % 10) * ((time - animationStartTime)/100 % 10) ) + "px";
    var t = (time - animationStartTime)/10 % 100;
    frog.style.backgroundPosition = - Math.floor(t / (100/2)) * 60+ "px";
    requestId = window.requestAnimationFrame(animate);
}
function start() {
    animationStartTime = window.performance.now();
    requestId = window.requestAnimationFrame(animate);
}
function stop() {
    if (requestId)
    window.cancelAnimationFrame(requestId);
    requestId = 0;
}

   requestAnimationFrame是一种方法,它会向浏览器发出信号代表一个基于脚本的动画须要经过将回调排入到动画帧请求回调列表队列中来从新取样。它拥有一张关于回调函数id的列表,这些回调函数正在等待执行。调用requestAnimationFrame返回id(requestId),id标识已经插入队列的回调。这个id能够以后用于取消回调,使用cancelAnimationFrame(id)来取消。

  requestAnimationFrame方法将参数昨晚回调,回调函数须要来执行去绘制一个新的动画帧(动画).回调,反过来,自己就是一个函数,接受动画更新时的时间戳做为参数(时间)。

  时间戳是调用优化性能的now方法的结果。你须要肯定任何其余你想要比较的时间测量也是DOMHighResTimeStamp -- 在上面的例子中,咱们调用window.performance.now和用animationStartTime变量存储结果。以后在动画函数(animate)中咱们比较每一帧的开始时间和当前时间来计算青蛙在每种状况下的位置。

  当编写动画时,万一你遇到旧教程,注意animationStartTime属性已经建立了,可是如今已经弃用了,因此你本身必须跟踪开始时间像我上面所示。

  能够访问个人requestAnimationFrame demo

 总结

  在这篇文章中咱们讨论了requestAnimationFrame是怎样提升JavaScript动画性能的,你怎样使用来兼容全部浏览器。我但愿这篇文章能激励你尝试更多更酷的动画效果--你能够更新任何用老方法实现的就动画代码。

练习:满屏的彩虹球

外文翻译连接:Better Performance With requestAnimationFrame

相关文章
相关标签/搜索