1. 程式人生 > >深入理解定時器系列第二篇——被譽為神器的requestAnimationFrame

深入理解定時器系列第二篇——被譽為神器的requestAnimationFrame

前面的話

  與setTimeoutsetInterval不同,requestAnimationFrame不需要設定時間間隔。這有什麼好處呢?為什麼requestAnimationFrame被稱為神器呢?本文將詳細介紹HTML5新增的定時器requestAnimationFrame

引入

  計時器一直是javascript動畫的核心技術。而編寫動畫迴圈的關鍵是要知道延遲時間多長合適。一方面,迴圈間隔必須足夠短,這樣才能讓不同的動畫效果顯得平滑流暢;另一方面,迴圈間隔還要足夠長,這樣才能確保瀏覽器有能力渲染產生的變化

  大多數電腦顯示器的重新整理頻率是60Hz,大概相當於每秒鐘重繪60次。大多數瀏覽器都會對重繪操作加以限制,不超過顯示器的重繪頻率,因為即使超過那個頻率使用者體驗也不會有提升。因此,最平滑動畫的最佳迴圈間隔是1000ms/60,約等於16.6ms

  而setTimeout和setInterval的問題是,它們都不精確。它們的內在執行機制決定了時間間隔引數實際上只是指定了把動畫程式碼新增到瀏覽器UI執行緒佇列中以等待執行的時間。如果佇列前面已經加入了其他任務,那動畫程式碼就要等前面的任務完成後再執行

  requestAnimationFrame採用系統時間間隔,保持最佳繪製效率,不會因為間隔時間過短,造成過度繪製,增加開銷;也不會因為間隔時間太長,使用動畫卡頓不流暢,讓各種網頁動畫效果能夠有一個統一的重新整理機制,從而節省系統資源,提高系統性能,改善視覺效果

特點

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

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

  【3】requestAnimationFrame是由瀏覽器專門為動畫提供的API,在執行時瀏覽器會自動優化方法的呼叫,並且如果頁面不是啟用狀態下的話,動畫會自動暫停,有效節省了CPU開銷

使用

  requestAnimationFrame的用法與settimeout很相似,只是不需要設定時間間隔而已。requestAnimationFrame使用一個回撥函式作為引數,這個回撥函式會在瀏覽器重繪之前呼叫。它返回一個整數,表示定時器的編號,這個值可以傳遞給cancelAnimationFrame用於取消這個函式的執行

requestID = requestAnimationFrame(callback); 
//控制檯輸出1和0
var timer = requestAnimationFrame(function(){
    console.log(0);
}); 
console.log(timer);//1

  cancelAnimationFrame方法用於取消定時器

//控制檯什麼都不輸出
var timer = requestAnimationFrame(function(){
    console.log(0);
}); 
cancelAnimationFrame(timer);

  也可以直接使用返回值進行取消

var timer = requestAnimationFrame(function(){
    console.log(0);
}); 
cancelAnimationFrame(1);

相容

  IE9-瀏覽器不支援該方法,可以使用setTimeout來相容

【簡單相容】

if (!window.requestAnimationFrame) {
    requestAnimationFrame = function(fn) {
        setTimeout(fn, 17);
    };    
}

【嚴格相容】

if(!window.requestAnimationFrame){
    var lastTime = 0;
    window.requestAnimationFrame = function(callback){
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0,16.7-(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);
    };
}

應用

  現在分別使用setInterval、setTimeout和requestAnimationFrame這三個方法制作一個簡單的進制度效果

【1】setInterval

<div id="myDiv" style="background-color: lightblue;width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn">run</button>
<script>
var timer;
btn.onclick = function(){
    clearInterval(timer);
    myDiv.style.width = '0';
    timer = setInterval(function(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML =     parseInt(myDiv.style.width)/5 + '%';    
        }else{
            clearInterval(timer);
        }        
    },16);
}
</script>

【2】setTimeout

<div id="myDiv" style="background-color: lightblue;width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn">run</button>
<script>
var timer;
btn.onclick = function(){
    clearTimeout(timer);
    myDiv.style.width = '0';
    timer = setTimeout(function fn(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML =     parseInt(myDiv.style.width)/5 + '%';
            timer = setTimeout(fn,16);
        }else{
            clearTimeout(timer);
        }    
    },16);
}
</script>

【3】requestAnimationFrame

<div id="myDiv" style="background-color: lightblue;width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn">run</button>
<script>
var timer;
btn.onclick = function(){
    myDiv.style.width = '0';
    cancelAnimationFrame(timer);
    timer = requestAnimationFrame(function fn(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML =     parseInt(myDiv.style.width)/5 + '%';
            timer = requestAnimationFrame(fn);
        }else{
            cancelAnimationFrame(timer);
        }    
    });
}
</script>