1. 程式人生 > >requestAnimationFrame理解與實踐

requestAnimationFrame理解與實踐

轉載:原文地址

背景

傳統的javascript動畫是通過定時器 setTimeout或者 setInterval實現的。但是定時器動畫一直存在兩個問題,第一個就是動畫的循時間環間隔不好確定,設定長了動畫顯得不夠平滑流暢,設定短了瀏覽器的重繪頻率會達到瓶頸,推薦的最佳迴圈間隔是17ms(大多數電腦的顯示器重新整理頻率是60Hz,1000ms/60);第二個問題是定時器第二個時間引數只是指定了多久後將動畫任務新增到瀏覽器的UI執行緒佇列中,如果UI執行緒處於忙碌狀態,那麼動畫不會立刻執行。為了解決這些問題,H5 中加入了 requestAnimationFrame;

優點

  • requestAnimationFrame 會把每一幀中的所有 DOM 操作集中起來,在一次重繪或迴流中就完成,並且重繪或迴流的時間間隔緊緊跟隨瀏覽器的重新整理頻率
  • 在隱藏或不可見的元素中,requestAnimationFrame 將不會進行重繪或迴流,這當然就意味著更少的 CPU、GPU 和記憶體使用量
  • requestAnimationFrame 是由瀏覽器專門為動畫提供的 API,在執行時瀏覽器會自動優化方法的呼叫,並且如果頁面不是啟用狀態下的話,動畫會自動暫停,有效節省了 CPU 開銷

場景

  • js動畫

requestAnimationFrame 本來就是為動畫而生的,所以在處理 js
動畫不在話下,與定時器的用法非常相似,下面是一個例子,點選元素時開始轉動,再次點選轉動速速增加。

var deg = 0;
var id;
var div = document.getElementById("div");
div.addEventListener('click', function () {
    var self = this;
    requestAnimationFrame(function change() {
        self.style.transform = 'rotate(' + (deg++) + 'deg)';
        id = requestAnimationFrame(change);
    });
});
document.getElementById('stop').onclick = function () {
    cancelAnimationFrame(id);
};
  • 大資料渲染

在大資料渲染過程中,比如表格的渲染,如果不進行一些效能策略處理,就會出現 UI 凍結現象,使用者體驗極差。有個場景,將後臺返回的十萬條記錄插入到表格中,如果一次性在迴圈中生成 DOM 元素,會導致頁面卡頓5s左右。這時候我們就可以用 requestAnimationFrame 進行分步渲染,確定最好的時間間隔,使得頁面載入過程中很流暢。

var total = 100000;
var size = 100;
var count = total / size;
var done = 0;
var ul = document.getElementById('list');

function addItems() {
    var li = null;
    var fg = document.createDocumentFragment();

    for (var i = 0; i < size; i++) {
        li = document.createElement('li');
        li.innerText = 'item ' + (done * size + i);
        fg.appendChild(li);
    }

    ul.appendChild(fg);
    done++;

    if (done < count) {
        requestAnimationFrame(addItems);
    }
};

requestAnimationFrame(addItems);

相容性

firefox、chrome、ie10以上, requestAnimationFrame 的支援很好,但不相容 IE9及以下瀏覽器,但是我們可以用定時器來做一下相容,以下是相容程式碼:

(function () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    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) {
            /*調整時間,讓一次動畫等待和執行時間在最佳迴圈時間間隔內完成*/
            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);
        };
}());

效能對比

以上面大資料渲染為例,我們向一個頁面中插入1萬條資料。
下面是用 setTimeout 後瀏覽器幀率:

在這裡插入圖片描述
下面是用 requestAnimationFrame 後瀏覽器幀率:

在這裡插入圖片描述
我們會發現,效能提升的還是很多的。所以還是很推薦使用 requestAnimationFrame;

下面是自己寫的一個小demo
demo介紹:公司一個專案需要金額動態變化,之前的太生硬了;

let gameBet = document.getElementById('gameBet');
		function numAnimate(num,maxNum){
			var id; 
			function numAdd(){
				num+=1; // 速度的計算可以為小數
				if(num >= maxNum){
					num = maxNum;	
					cancelAnimationFrame(id); // 清除requestAnimationFrame
				}else {
					id = requestAnimationFrame(numAdd); // 把它賦值變數 
				}
				gameBet.innerHTML = num
				
			}
			numAdd();
		}
		// 執行
		numAnimate(0,1000)