1. 程式人生 > >效能更好的js動畫實現方式——requestAnimationFrame

效能更好的js動畫實現方式——requestAnimationFrame

用js來實現動畫,我們一般是藉助setTimeout或setInterval這兩個函式,css3動畫出來後,我們又可以使用css3來實現動畫了,而且效能和流暢度也得到了很大的提升。但是css3動畫還是有不少侷限性,比如不是所有屬性都能參與動畫、動畫緩動效果太少、無法完全控制動畫過程等等。所以有的時候我們還是不得不使用setTimeout或setInterval的方式來實現動畫,可是setTimeout和setInterval有著嚴重的效能問題,雖然某些現代瀏覽器對這兩函個數進行了一些優化,但還是無法跟css3的動畫效能相提並論。這個時候,就該requestAnimationFrame出馬了。

requestAnimationFrame 是專門為實現高效能的幀動畫而設計的一個API,目前已在多個瀏覽器得到了支援,包括IE10+,Firefox,Chrome,Safari,Opera等,在移動裝置上,ios6以上版本以及IE mobile 10以上也支援requestAnimationFrame,唯一比較遺憾的是目前安卓上的原生瀏覽器並不支援requestAnimationFrame,不過對requestAnimationFrame的支援應該是大勢所趨了,安卓版本的chrome 16+也是支援requestAnimationFrame的。

requestAnimationFrame 比起 setTimeout、setInterval的優勢主要有兩點:

1、requestAnimationFrame 會把每一幀中的所有DOM操作集中起來,在一次重繪或迴流中就完成,並且重繪或迴流的時間間隔緊緊跟隨瀏覽器的重新整理頻率,一般來說,這個頻率為每秒60幀。

2、在隱藏或不可見的元素中,requestAnimationFrame將不會進行重繪或迴流,這當然就意味著更少的的cpu,gpu和記憶體使用量。

像setTimeout、setInterval一樣,requestAnimationFrame是一個全域性函式。呼叫requestAnimationFrame後,它會要求瀏覽器根據自己的頻率進行一次重繪,它接收一個回撥函式作為引數,在即將開始的瀏覽器重繪時,會呼叫這個函式,並會給這個函式傳入呼叫回撥函式時的時間作為引數。由於requestAnimationFrame的功效只是一次性的,所以若想達到動畫效果,則必須連續不斷的呼叫requestAnimationFrame,就像我們使用setTimeout來實現動畫所做的那樣。requestAnimationFrame函式會返回一個資源識別符號,可以把它作為引數傳入cancelAnimationFrame函式來取消requestAnimationFrame的回撥。怎麼樣,是不是也跟setTimeout的clearTimeout很相似啊。

所以,可以這麼說,requestAnimationFrame就是一個性能優化版、專為動畫量身打造的setTimeout,不同的是requestAnimationFrame不是自己指定回撥函式執行的時間,而是跟著瀏覽器內建的重新整理頻率來執行回撥,這當然就能達到瀏覽器所能實現動畫的最佳效果了。

目前,各個支援requestAnimationFrame的瀏覽器有些還是自己的私有實現,所以必須加字首,對於不支援requestAnimationFrame的瀏覽器,我們只能使用setTimeout,因為兩者的使用方式幾近相同,所以這兩者的相容並不難。對於支援requestAnimationFrame的瀏覽器,我們使用requestAnimationFrame,而不支援的我們優雅降級使用傳統的setTimeout。把它們封裝一下,就能得到一個統一相容各大瀏覽器的API了。

複製程式碼
var lastTime = 0;
var prefixes = 'webkit moz ms o'.split(' '); //各瀏覽器字首

var requestAnimationFrame = window.requestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame;

var prefix;
//通過遍歷各瀏覽器字首,來得到requestAnimationFrame和cancelAnimationFrame在當前瀏覽器的實現形式
for( var i = 0; i < prefixes.length; i++ ) {
    if ( requestAnimationFrame && cancelAnimationFrame ) {
      break;
    }
    prefix = prefixes[i];
    requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
    cancelAnimationFrame  = cancelAnimationFrame  || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ];
}

//如果當前瀏覽器不支援requestAnimationFrame和cancelAnimationFrame,則會退到setTimeout
if ( !requestAnimationFrame || !cancelAnimationFrame ) {
    requestAnimationFrame = function( callback, element ) {
      var currTime = new Date().getTime();
      //為了使setTimteout的儘可能的接近每秒60幀的效果
      var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); 
      var id = window.setTimeout( function() {
        callback( currTime + timeToCall );
      }, timeToCall );
      lastTime = currTime + timeToCall;
      return id;
    };
    
    cancelAnimationFrame = function( id ) {
      window.clearTimeout( id );
    };
}

//得到相容各瀏覽器的API
window.requestAnimationFrame = requestAnimationFrame; 
window.cancelAnimationFrame = cancelAnimationFrame;
複製程式碼

這樣子我們就能在所有瀏覽器上使用requestAnimationFrame和cancelAnimationFrame了。

下面舉個簡單的例子來說明怎麼運用requestAnimationFrame進行動畫,下面的程式碼會將id為demo的div以動畫的形式向右移動到300px

複製程式碼
<div id="demo" style="position:absolute; width:100px; height:100px; background:#ccc; left:0; top:0;"></div>

<script>
var demo = document.getElementById('demo');
function rander(){
    demo.style.left = parseInt(demo.style.left) + 1 + 'px'; //每一幀向右移動1px
}
requestAnimationFrame(function(){
    rander();
    //當超過300px後才停止
    if(parseInt(demo.style.left)<=300) requestAnimationFrame(arguments.callee);
});
</script>
複製程式碼

參考資料: