谈谈requestAnimationFrame的动画循环

kart 8年前
   <h2>一. 动画的循环间隔</h2>    <p>编写动画循环的关键,是要知道延迟时间多长合适。一方面,循环时间必须足够短,这样才能保证动画效果更平滑流畅;另一方面,循环还要足够长,这样才 能保证浏览器有能力渲染产生的变化。大多数显示器的刷新频率是60Hz,相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重 绘频率,因为即使超过了这个频率,用户体验也不会有提升。</p>    <p>因此最平滑动画的最佳循环间隔是1000ms/60,约等于17ms。以这个循环间隔重绘的动画是平滑的,因为这个速度最接近浏览器的最高限速。为了适应17ms的循环间隔,多重动画可能需要加以节制,以便不会完成得太快。</p>    <p>虽然与使用多组<code>setTimeout()</code>相比,使用<code>setInterval()</code>的动画循环效率更高。但是无论<code>setTimeout()</code>还是<code>setInterval()</code>都 不十分精确。为它们传入的第二个参数,实际上只是指定了把动画代码添加到浏览器UI线程队列以等待执行的时间。如果队列前面已经加入了其他任务,那动画代 码就要等前面的任务执行完成后再执行。如果UI线程繁忙,比如忙于处理用户操作,那么即使把代码加入队列也不会立即执行。</p>    <p>因此,知道什么时候绘制下一帧是保证动画平滑的关键。然而,面对不十分精确的<code>setTimeout(</code>)和<code>setInterval()</code>,开发人员至今都没有办法确保浏览器按时绘制下一帧。以下是几个浏览器的计时器精度:</p>    <ul>     <li>IE8及其以下版本浏览器: 15.6ms;</li>     <li>IE9及其以上版本浏览器:4ms;</li>     <li>Firefox和Safari:10ms;</li>     <li>Chrome:4ms。</li>    </ul>    <p>更为复杂的是,浏览器开始限制后台标签页或不活动标签页的计数器。因此,即使你优化了循环间隔,可能仍然只能接近你想要的效果。</p>    <h2>二. requestAnimationFrame()</h2>    <p><strong>Mozilla</strong>的 <strong>Robert O’Callahan</strong> 指出,CSS变换动画的优势在于浏览器知道动画什么时候开始,因此会计算出正确的循环间隔,在适当的时候刷新UI。而对于JavaScript动画,浏览器就无从知晓什么时候开始。</p>    <p>因此<strong>Robert O’Callahan</strong>的方案是,创建一个新方法<code>mozRequestAnimationFrame()</code>,通过它告诉浏览器某些代码将要执行动画。这样浏览器可以在运行某些代码后进行适当的优化。</p>    <p>与<code>setTimeout()</code>和<code>setInterval()</code>方法不同,<code>requestAnimationFrame(</code>)不需要调用者指定帧速率,浏览器会自行决定最佳的帧效率。</p>    <p><code>requestAnimationFrame()</code>方法接收一个参数,即在重绘屏幕前调用以个函数。这个函数负责改变下一次重绘时的DOM样式。为了创建动画循环,可以像使用<code>setTimeout()</code>一样,把多个对<code>requestAnimationFrame()</code>的调用连缀起来。如:</p>    <pre>  <code class="language-javascript">function drawFrame() {      window.requestAnimationFrame(drawFrame);      // animation code...  }  window.requestAnimationFrame(drawFrame);  </code></pre>    <h2>三. requestAnimationFrame()的兼容性</h2>    <h3>3.1 requestAnimationFrame()的兼容性封装:</h3>    <p>由于<code>mozRequestAnimationFrame()</code>是HTML5的新功能,目前各大浏览器的支持情况各异。具体兼容情况个参考<a href="/misc/goto?guid=4959677079906519925">http://caniuse.com/#search=RequestAnimationFrame</a>。如果希望代码具备更好的跨平台性,可以考虑使用下面的代码实现各平台兼容性:</p>    <pre>  <code class="language-javascript">if(!window.requestAnimationFrame) {      window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||      window.mozRequestAnimationFrame ||      window.oRequestAnimationFrame ||      window.msRequestAnimationFrame ||      function(callback) {          var self = this, start, finish;          return window.setTimeout(function() {              start = +new Date();              callback(start);              finish = +new Date();              self.timeout = 1000/60 - (finish - start);          }, self.timeout);      });  }  </code></pre>    <p>这段代码先检查了<code>window.requestAnimationFrame</code>函数的定义是否存在。如果不存在,就遍历已知的各种浏览器实现并替代该函数。如果还是找不到一个与浏览器相关的实现,它最终会采用基于JavaScript定时器的动画以每秒60帧的间隔调用<code>setTimeout</code>函数。</p>    <p><code>mozRequestAnimationFrame()</code>会接收一个时间码(从1970年1月1日起至今的毫秒数),表示下一次重绘的实际发生时间。这样,<code>mozRequestAnimationFrame()</code>就会根据这个时间码设定将来的某个时刻进行重绘。</p>    <p>但是<code>webkitRequestAnimationFrame()</code>和<code>msRequestAnimationFrame()</code>不会给回调函数传递时间码,因此无法知道下一次重绘将发生在什么时间。</p>    <p>如果要计算两次重绘的时间间隔,Firefox中可以使用既有的时间码,而在Chrome和IE则可以使用不太精确地<code>Date()</code>对象。</p>    <h3>3.2 cancelRequestAnimFrame()的兼容性封装:</h3>    <p>W3C也提供了<code>cancelRequestAnimationFrame()</code>方法,用于取消回调函数。<code>requestAnimationFrame()</code>方法会返回一个对象,用做标识回掉函数身份。若要取消回调函数的执行,可将其传给<code>cancelRequestAnimationFrame()</code>。</p>    <pre>  <code class="language-javascript">window.cancelRequestAnimFrame = ( function() {      return window.cancelAnimationFrame ||          window.webkitCancelRequestAnimationFrame ||          window.mozCancelRequestAnimationFrame ||          window.oCancelRequestAnimationFrame ||          window.msCancelRequestAnimationFrame ||          clearTimeout;  } )();  </code></pre>    <h3>3.3 requestAnimationFrame()升级版封装方法:</h3>    <p>另外还有一种更优雅的<code>requestAnimationFrame()</code>的兼容性封装方法: (引用自:<a href="/misc/goto?guid=4959677079988915282">https://gist.github.com/paulirish/1579671</a>)</p>    <pre>  <code class="language-javascript">(function() {      var lastTime = 0;      var vendors = ['ms', 'moz', 'webkit', 'o'];      for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {          window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];          window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']  || window[vendors[x]+'CancelRequestAnimationFrame'];      }        if (!window.requestAnimationFrame)          window.requestAnimationFrame = function(callback, element) {              var currTime = new Date().getTime();              var timeToCall = Math.max(0, 16 - (currTime - lastTime));              var id = window.setTimeout(function() { callback(currTime + timeToCall); },                 timeToCall);              lastTime = currTime + timeToCall;              return id;          };        if (!window.cancelAnimationFrame)          window.cancelAnimationFrame = function(id) {              clearTimeout(id);          };  }());  </code></pre>    <p>来自:http://www.dengzhr.com/js/937</p>