canvas玩多了後,就會自動的要開始考慮效能問題了。怎麼優化canvas的動畫呢?
【使用快取】
使用快取也就是用離屏canvas進行預渲染了,原理很簡單,就是先繪製到一個離屏canvas中,然後再透過drawImage把離屏canvas畫到主canvas中。可能看到這很多人會誤解,這不是寫遊戲裡面用的很多的雙緩衝機制麼?
其實不然,雙緩衝機制是遊戲程式設計中為了防止畫面閃爍,因此會有一個顯示在使用者面前的畫布以及一個後台畫布,進行繪製時會先將畫面內容繪製到後台畫布中,再將後台畫布里的數據繪製到前台畫布中。這就是雙緩衝,但是canvas中是沒有雙緩衝的,因為現代瀏覽器基本上都是內建了雙緩衝機制。所以,使用離屏canvas並不是雙緩衝,而是把離屏canvas當成快取區。把需要重複繪製的畫面資料快取起來,減少呼叫canvas的API的消耗。
眾所周知,調用canvas的API很消耗性能,所以,當我們要繪製一些重複的畫面數據時,妥善利用離屏canvas對性能方面有很大的提升,可以看下下面的DEMO
1 、 沒使用快取
2、 使用了快取但是沒有設定離屏canvas的寬高
3 、 使用了快取但是沒有設定離螢幕canvas的寬高
4 、 使用了快取且設定了離屏canvas的寬高
可以看到上面的DEMO的性能不一樣,下面分析一下原因:為了實現每個圈的樣式,所以繪製圈圈時我用了循環繪製,如果沒用啟用緩存,當頁面的圈圈數量達到一定時,動畫每一幀就要大量呼叫canvas的API,要進行大量的計算,這樣再好的瀏覽器也會被拖垮啦。
XML/HTML Code複製內容到剪貼簿
- ctx.save();
-
var j
;
-
for(var 🎜>this.r;i =borderWidth){
- ctx.beginPath();
🎜>.color[j];
地
ctx.中使用();
j ;
-
}
-
ctx.restore();
所以,我的方法很簡單,每個圈圈物件裡面給他一個離屏canvas作伺服器區。
除了創建離屏canvas作為伺服器之外,下面的程式碼有一點很關鍵,就是要設定離屏canvas的寬度和高度,canvas生成後的預設大小是300X150;對於我的程式碼中每個伺服器上圈超過圈物件半徑最大也不會80,所以300X150的大小明顯會造成很多空白區域,會造成資源浪費,所以就要設定一下離截圖的寬度和高度,以便跟緩存上元素大小一致,這樣也有利於提高動畫效能。物體也覺得卡。
- XML/HTML 程式碼將內容複製到剪貼簿
-
var 球 = 函數(x ,], 🎜>
- this.x = x;
- this.y = y;
- this.vx vx;
- this.vy = vy;
- this.r = this.r =
-
this.color = [];
-
this.cacheCanvas =cre
這
this.cacheCtx- 這this.cacheCtx這this.cacheCtx這個this.cacheCtx
- A.cacheCtx
this.cacheCanvas.width@=
-
this.cacheCanvas.heightthis.cacheCanvas.height.
- var num
for(var j
- =j= num;j ){
this.color.push("rgba(" getZ(getRandom(0,255)) ",push("rgba(" getZ(getRandom(0,255)) ",push("rgba(" getZ(getRandom(0,255)) ",push("rgba(" getZ(getRandom(0,255)) "、," getZ(Random)( ");
}
-
- this.useCache = useCache;
-
if(useCache){
this.cache();
-
}
-
}
-
當我實例化圈圈物件時,直接呼叫快取方法,把複雜的圈圈直接畫到圈圈物件的離屏canvas中保存起來。
XML/HTML Code複製內容到剪貼簿
- cache:function(){
- this.cacheCtx.save();
-
var j
this.cacheCtx.lineWidth
- for(var ithis.r;i =borderWidth){
this.cacheCtx.beginPath(); - ‧
this.cacheCtx.arc(this.r >
this.cacheCtx.stroke();
j ;
} -
this.cacheCtx.restore(); -
}
然後在接下來的動畫中,我只需要把圈圈物件的離屏canvas畫到主canvas中,這樣,每一幀調用的canvasAPI就只有這麼一句話:-
XML/HTML Code- 複製內容到剪貼簿
-
- ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);
-
跟之前的for迴圈繪製比起來,實在是快太多了。所以當需要重複繪製向量圖的時候或繪製多個圖片的時候,我們都可以合理利用離屏canvas來預先把畫面資料快取起來,在接下來的每一幀中就能減少很多沒必要的消耗性能的操作。 -
下面貼出1000個圈圈物件流暢版代碼:
XML/HTML Code複製內容到剪貼簿
- html>
-
html lang="en" >
-
頭>
-
元 字符集 字符集 字符集
-
字符集 字符集 字符集
- 字符集
>-
-
樣式>-
-
身體{
-
已填入:0;
-
邊距:0;
-
溢位:隱藏;
-
}
-
#cas{
顯示: 塊體;
背景色:rgba(0,0,0,0);
邊距: 自動;
邊框: 1px 實心;
}
-
style>
- title>測試>測試>測試
- >
head
- >
body
- >
div
- > > > > > > > > canvas id
-
idid width="800" >"600">瀏覽器不支援canvascanvas
canvas-
>canvas>>
-
- div style style
- style style >
1000圈物件也不卡-
div>div>
-
div>
-
- script>
-
var testBox = function = function
= - function var canvas
= -
docum 🎜>
ctx
= -
borderWidth
= borderWidth = borderWidth
Balls = [];
var ball =
this.x
this.y = y;
this.vx vx; this.vy = vy;
-
this.r = this.r =
- this.color = [];
- this.cacheCanvas =cre
這
- this.cacheCtx這this.cacheCtx這this.cacheCtx這個
this.cacheCtx-
A.cacheCtx
this.cacheCanvas.width@=
-
this.cacheCanvas.heightthis.cacheCanvas.height.
-
var num
for(var
j-
=j= num;j ){
this.color.push("rgba(" getZ(getRandom(0,255)) ",push("rgba(" getZ(getRandom(0,255)) ",push("rgba(" getZ(getRandom(0,255)) ",push("rgba(" getZ(getRandom(0,255)) "、," getZ(Random)( ");
} -
- this.useCache
= useCache; -
if(useCache){
this.cache(); -
} -
}
-
- 函數 getZ(num){
- var 圓形;
-
四捨五入 (0.5
// 以雙位元非使用。 -
-
四捨五入 > (0.5 (0.5 );
// 最後,且以左移。
-
四捨五入-
🎜> 0;
以上為四捨五入中;
}
-
-
ball.prototype = {
- paint:function(ctx){
- if(!this.useCache){
- ctx.save();
-
var j
;
-
for(var 🎜>this.r;i =borderWidth){
- ctx.beginPath();
🎜>.color[j];
地
ctx.中使用();
j ;
-
}
-
ctx.restore();
} 其他{
ctx.drawImage(this.cacheCanvas
-
}
-
},
-
- 儲存:function(){
- this.cacheCtx.save();
-
var j
this.cacheCtx.lineWidth
- 以(var i this.r;i =borderWidth){
this.cacheCtx.beginPath(); - 這
this.cacheCtx.arc(this.r this.r
this.cacheCtx.且中風();
j ;
- }
-
this.cacheCtx.restore();
},
-
move:function(){ -
this.x = this.vx; -
this.y = this.vy; -
if(this.x-
>這個.r
- ){
- 這
這個- 這樣這個.r
?this.r:(canvas.width-this.r); -
- this
}
-
if(this.y>這個.r){
這
這個- 這樣這個.r?this.r:(canvas.height-this.r);
}
-
this.paint(ctx);
} -
} -
-
var - 遊戲
= { -
- init:function(){
- 以(var i1000
;i ){ -
var - b
Balls.push(b);
}
},
-
update:function(){
ctx.clearRect(0,0,canvas.width,canvas.height); 以(var
- i
Balls.length- ;i ){
- Balls[i].move();
- }
-
},
-
- loop:function(){
-
var _this_this
- this.update();
- RAF(function(){
- _this.loop();
- })
- },
-
- start:function(){
- this.init();
- this.loop();
- }
- }
-
-
window.RAF = (function(){
= (function(){ - 地與中返回window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || 函數(回呼){window.setTimeout(回調,1000/60); };
-
})();
-
-
返回遊戲;
-
}();
-
-
函數 getRandom(a, b){
-
返回 Math.random()*(b-a) a;
-
}
-
-
window.onload = 函數
testBox.start(); -
} -
-
腳本>
-
身體>
-
html>
離屏canvas還有一個注意事項,如果你做的效果是會將物件不停地創建和銷毀,請慎重使用離屏canvas,至少不要像我上面寫的那樣給每個對象的屬性綁定離屏canvas。
因為如果這樣綁定,當物件被銷毀時,離屏canvas也會被銷毀,而大量的離屏canvas不停地被創建和銷毀,會導致canvas buffer耗費大量GPU資源,容易造成瀏覽器崩潰或嚴重卡幀現象。解決方法就是弄一個離屏canvas數組,預先裝進足夠數量的離屏canvas,僅將仍然存活的物件快取起來,當物件被銷毀時,再解除快取。這樣就不會導致離屏canvas被銷毀了。
【使用requestAnimationFrame】
這個就不具體解釋了,估計很多人都知道,這個才是做動畫的最佳循環,而不是setTimeout或setInterval。直接貼出相容性寫法:
XML/HTML Code複製內容到剪貼簿
- window.RAF = (function(){
- return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame setTimeout(callback, 1000 / 60); };
- })();
【避免浮點運算】
雖然javascript提供了很方便的一些取整方法,像Math.floor,Math.ceil,parseInt,但是,國外友人做過測試,parseInt這個方法做了一些額外的工作(比如檢測數據是不是有效的數值,parseInt 甚至先將參數轉換成了字串!),所以,直接用parseInt的話相對來說比較消耗性能,那怎樣取整呢,可以直接用老外寫的很巧妙的方法了:
JavaScript Code複製內容到剪貼簿
1.rounded = (0.5 somenum) | 0;
2.rounded = ~~ (0.5 somenum); 3.rounded = (0.5 somenum)
運算子不懂的可以直接戳:
http://www.w3school.com.cn/js/pro_js_operators_bitwise.asp
裡面有詳細解釋
【盡量減少canvasAPI的呼叫】
作粒子效果時,盡量少使用圓,最好使用方形,因為粒子太小,所以方形看上去也跟圓差不多。至於原因,很容易理解,我們畫一個圓需要三個步驟:先beginPath,然後用arc畫弧,再用fill進行填充才能產生一個圓。但是畫方形,只需要一個fillRect就可以了。雖然只是差了兩個調用,當粒子物件數量達到一定時,這性能差距就會顯示出來了。
還有一些其他注意事項,我就不一一列舉了,因為谷歌上一搜也挺多的。我這也算是一個給自己做下記錄,主要是記錄快取的用法。想要提升canvas的效能最主要的還是得注意程式碼的結構,減少不必要的API調用,在每一幀中減少複雜的運算或把複雜運算由每一幀算一次改成數幀算一次。同時,上面所述的快取用法,我因為貪圖方便,所以是每個物件一個離屏canvas,其實離屏canvas也不能用的太氾濫,如果用太多離屏canvas也會有效能問題,請盡量合理利用離屏canvas。
原始碼位址:
https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Other-demo/cache