大概半年前,無意中在網上看到一個新的js函數requestAnimationFrame,據說,此函數可以優化傳統的js動畫效果,似乎是未來js動畫的新方向。
當時我所在的項目正好用到了和js動畫有關的技術,於是在網上查閱了一些相關資料。雖然國內外都有人寫過一些關於這個js函數的文章,但大多都只是簡要說明工作原理,使用方式等等,一直都沒有找到驗證其優勢所在的示例。
今天我就自己寫兩個testcase驗證requestAnimationFrame的優勢所在。
關於requestAnimationFrame這個函數在MDN上的說明是“告訴瀏覽器,你想要執行一個動畫,該請求要求瀏覽器提前安排一下下一幀動畫顯示時需要進行的瀏覽器窗口的重繪”。也就是說,調用這個api就表示告訴瀏覽器,下次重繪頁面時,記得執行我剛剛傳給你的那個邏輯。
這樣做一個最大的好處就是可以避免不必要的過度重繪,關於過度重繪的產生原因MSDN上已經說得很清楚了。
於是,我的測試思路是,用js構造若干個獨立的動畫,每個動畫都有一個定時器去控制動畫的執行,動畫的效果就是通過修改一個div的位置坐標,使該div圍繞一點做圓周運動。
定時器用兩種不同的方式實現,一種用傳統的setTimeout函數,每個20ms觸發一次重繪邏輯。另一種用requestAnimationFrame觸發重繪邏輯。(代碼見文末)
下面兩幅圖為同時運行1500個動畫,分別使用requestAnimationFrame和setTimeout時的效果:
使用 setTimeout
使用 requestAnimationFrame
下表為在不同的動畫數目情況下,使用requestAnimationFrame和setTimeout時瀏覽器的渲染幀數對比,單位:FPS
Animation count |
500 |
600 |
700 |
800 |
900 |
1000 |
1100 |
1200 |
1300 |
1400 |
1500 |
reqestanimationframe |
30 |
30 |
28 |
26 |
20 |
20 |
20 |
20 |
19 |
17 |
15 |
setTimeout |
34 |
32 |
29.5 |
27 |
25.5 |
21.8 |
19.7 |
16 |
14.7 |
13.1 |
12 |
由上表可以看出,當animation count大於1100的時候,使用requestAnimationFrame的性能是要優於使用setTimeout的,可是當animation count小於1000的時候,使用requestAnimationFrame的性能反而要差於用傳統的setTimeout
同時,在上面的測試案例中,無論動畫數目是多少,我們使用setTimeout時的延遲間隔始終都是20ms,但在一些情況下適當增加這個時間間隔,setTimeout函數還能得到更好的效果。比如當animation count為1100時,如果把這個間隔調整到40ms,瀏覽器的幀率可以達到25.5 FPS,明顯優於使用requestAnimationFrame時的20 FPS。
看到這里估計大家和我一樣都很失望吧,這個傳說中實現了各種優化的新api怎么表現如此差強人意呢?
結合上次我寫的關於統一幀管理的blog《使用統一幀管理優化前端性能》,我又想到另一種測試場景。
仍然是用js構造若干個獨立的動畫,但所有這些動畫都共用同一個定時器去定時更新自己的狀態並重繪,詳見代碼中的“test case 02”部分。
還是貼兩幅同時運行1500個動畫,分別使用requestAnimationFrame和setTimeout時的效果:
使用 setTimeout
使用 RequestAnimationFrame
同時附上不同的動畫數目情況下,使用requestAnimationFrame和setTimeout時瀏覽器的渲染幀數對比,單位:FPS
Animation count |
500 |
600 |
700 |
800 |
900 |
1000 |
1100 |
1200 |
1300 |
1400 |
1500 |
reqestanimationframe |
30 |
30 |
30 |
30 |
29.5 |
28 |
20 |
20 |
20 |
20 |
20 |
setTimeout |
37.5 |
35 |
34.4 |
32 |
31.5 |
29.5 |
29 |
26.5 |
25 |
24.3 |
23 |
由上表可見,當使用一個定時器控制所有動畫的時候,使用setTimeout函數在各種動畫數目場景下,其效果均優於使用requestAnimationFrame函數。
通過上面的兩個測試案例,我們可以看到requestAnimationFrame函數似乎並不像大家所想的那樣能給js動畫帶來性能上的大幅提升。雖然MDN和MSDN上對這個api的原理說明都讓我覺得它確實是有用的,但實際測試的效果卻並不能讓我滿意。也許各大瀏覽器廠商還會繼續優化這個api的實現,使其能真正達到預期的效果吧。
BTW,其實到這里我自己都很懷疑是不是我的測試案例有問題?如果大家對測試案例有任何意見或建議,還請不吝賜教啊!
以上所有測試的運行環境為 Windows 7 Ultimate sp1 x64 + Chrome 25.0.1364.97 + Intel Core i3 530(2.93GHz) + 4G RAM
代碼
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <style type="text/css"> 5 html,body{height:100%;width:100%} 6 div{width:20px;height:20px;background-color:black;position:absolute;display:inline-block} 7 </style> 8 </head> 9 <body> 10 <div id="content" style="height:100%;width:100%;background-color:#979797;overflow:hidden;position:relative"></div> 11 <script type="text/javascript"> 12 var cArray = new Array(); 13 14 for (var i = 0; i < 1500; i++) { 15 var x = parseInt(Math.random() * document.body.clientWidth); 16 var y = parseInt(Math.random() * document.body.clientHeight); 17 var id = newGuid(); 18 content.innerHTML += '<div id="' + id + '"></div>'; 19 cArray.push(new Clock(x, y, 100, id)); 20 } 21 22 function Clock(x, y, r, id) { 23 this.start = new Date(); 24 this.r = r; 25 this.x = x; 26 this.y = y; 27 this.offsetX = r; 28 this.offsetY = 0; 29 this.id = id; 30 this.draw = function(){ 31 //update data 32 var timespan = new Date() - this.start; 33 var offsetR = ((timespan % 36000) % 720) / 360 * Math.PI; 34 this.offsetX = this.r * Math.cos(offsetR); 35 this.offsetY = this.r * Math.sin(offsetR); 36 //draw 37 var dom = document.getElementById(this.id); 38 dom.style.left = this.x + this.offsetX + 'px'; 39 dom.style.top = this.y + this.offsetY + 'px'; 40 41 //var that = this; //test case 01 42 //setTimeout(function(){that.draw()}, 40); //test case 01 - 1 43 //requestAnimationFrame(function(){that.draw()}); //test case 01 - 2 44 } 45 //this.draw(); //test case 01 46 } 47 48 function renderLoop(){ 49 for (var i = 0; i < cArray.length; i++) { 50 cArray[i].draw(); 51 } 52 //setTimeout(renderLoop, 20); //test case 02 - 1 53 requestAnimationFrame(renderLoop); //test case 02 - 2 54 } 55 renderLoop(); //test case 02 56 57 function newGuid() { 58 var guid = ""; 59 for (var i = 1; i <= 32; i++) { 60 var n = Math.floor(Math.random() * 16.0).toString(16); 61 guid += n; 62 if ((i == 8) || (i == 12) || (i == 16) || (i == 20)) 63 guid += "-"; 64 } 65 return guid; 66 } 67 </script> 68 </body> 69 </html>
如需轉載,請注明轉自 http://www.cnblogs.com/silenttiger/archive/2013/06/19/3143785.html
歡迎關注我的微信公眾號:老虎的小窩