總原則:
????????在移動(dòng)APP中,因?yàn)槭謾C(jī)硬件性能有限,其實(shí)不宜做太多特效,應(yīng)該往簡(jiǎn)潔突出重點(diǎn)的方向考慮。
1 性能建議
英文引文地址:
http://www.html5rocks.com/en/tutorials/canvas/performance/
提高HTML5 canvas性能的幾種方法!
http://blog.csdn.net/zyz511919766/article/details/7401792
1.1 預(yù)渲染
1.PRE-RENDER TO AN OFF-SCREEN CANVAS
????????預(yù)渲染即在一個(gè)或者多個(gè)臨時(shí)的不會(huì)在屏幕上顯示的canvas中渲染臨時(shí)的圖像,然后再把這些不可見(jiàn)的canvas作為圖像渲染到可見(jiàn)的canvas中。對(duì)于計(jì)算機(jī)圖形學(xué)比較熟悉的朋友應(yīng)該都知道,這個(gè)技術(shù)也被稱(chēng)做display list。
????????沒(méi)有預(yù)渲染的情況:
//?canvas,?context?are?defined
function?render()?{
????drawMario(context);
????requestAnimationFrame(render);
}
????????預(yù)渲染的情況:
var?m_canvas?=?document.createElement('canvas');
m_canvas.width?=?64;
m_canvas.height?=?64;
var?m_context?=?m_canvas.getContext(‘2d’);
drawMario(m_context);
function?render()?{
????context.drawImage(m_canvas,?0,?0);
????requestAnimationFrame(render);
}??
????????關(guān)于requestAnimationFrame的使用方法將在后續(xù)部分做詳細(xì)的講述。當(dāng)渲染操作(例如上例中的drawmario)開(kāi)銷(xiāo)很大時(shí)該方法將非常有效。其中很耗資源的文本渲染操作就是一個(gè)很好的例子。
????????要確保臨時(shí)的canvas恰好適應(yīng)你準(zhǔn)備渲染的圖片的大小,否則過(guò)大的canvas會(huì)導(dǎo)致我們獲取的性能提升被將一個(gè)較大的畫(huà)布復(fù)制到另外一個(gè)畫(huà)布的操作帶來(lái)的性能損失所抵消掉。
????????上述的測(cè)試用例中緊湊的canvas相當(dāng)?shù)男。?/p>
can2.width?=?100;
can2.height?=?40;
????????如下寬松的canvas將導(dǎo)致糟糕的性能:
can3.width?=?300;??
1.2 多條指令一次載入
2.BATCH CANVAS CALLS TOGETHER
????????因?yàn)槔L圖是一個(gè)代價(jià)昂貴的操作,因此,用一個(gè)長(zhǎng)的指令集載入將繪圖狀態(tài)機(jī)載入,然后再一股腦的全部寫(xiě)入到video緩沖區(qū)。這樣會(huì)會(huì)更佳有效率。
????????例如,當(dāng)需要畫(huà)對(duì)條線(xiàn)條時(shí)先創(chuàng)建一條包含所有線(xiàn)條的路經(jīng)然后用一個(gè)draw調(diào)用將比分別單獨(dú)的畫(huà)每一條線(xiàn)條要高效的多:
for?(var?i?=?0;?i?<?points.length?-?1;?i++)?{
??var?p1?=?points[i];
??var?p2?=?points[i+1];
? context.beginPath();
? context.moveTo(p1.x,?p1.y);
? context.lineTo(p2.x,?p2.y);
? context.stroke();
}
????????通過(guò)繪制一個(gè)包含多條線(xiàn)條的路徑我們可以獲得更好的性能:
context.beginPath();
for?(var?i?=?0;?i?<?points.length?-?1;?i++)?{
??var?p1?=?points[i];
??var?p2?=?points[i+1];
? context.moveTo(p1.x,?p1.y);
? context.lineTo(p2.x,?p2.y);
}
context.stroke();
????????這個(gè)方法也適用于HTML5 canvas。比如,當(dāng)我們畫(huà)一條復(fù)雜的路徑時(shí),將所有的點(diǎn)放到路徑中會(huì)比分別單獨(dú)的繪制各個(gè)部分要高效的多(jsperf):
????????然而,需要注意的是,對(duì)于canvas來(lái)說(shuō)存在一個(gè)重要的例外情況:若欲繪制的對(duì)象的部件中含有小的邊界框(例如,垂直的線(xiàn)條或者水平的線(xiàn)條),那么單獨(dú)的渲染這些線(xiàn)條或許會(huì)更加有效(jsperf)
1.3 避免不必要的狀態(tài)切換
3.AVOID UNNECESSARY CANVAS STATE CHANGES
????????HTML5 canvas元素是在一個(gè)狀態(tài)機(jī)之上實(shí)現(xiàn)的。狀態(tài)機(jī)可以跟蹤諸如fill、stroke-style以及組成當(dāng)前路徑的previous points等等。在試圖優(yōu)化繪圖性能時(shí),我們往往將注意力只放在圖形渲染上。實(shí)際上,操縱狀態(tài)機(jī)也會(huì)導(dǎo)致性能上的開(kāi)銷(xiāo)。
????????例如,如果你使用多種填充色來(lái)渲染一個(gè)場(chǎng)景,按照不同的顏色分別渲染要比通過(guò)canvas上的布局來(lái)進(jìn)行渲染要更加節(jié)省資源。為了渲染一副條紋的圖案,你可以這樣渲染:用一種顏色渲染一條線(xiàn)條,然后改變顏色,渲染下一條線(xiàn)條,如此反復(fù):
for?(var?i?=?0;?i?<?STRIPES;?i++)?{
? context.fillStyle?=?(i?%?2???COLOR1?:?COLOR2);
? context.fillRect(i?*?GAP,?0,?GAP,?480);
}??
????????也可以先用一種顏色渲染所有的偶數(shù)線(xiàn)條再用另外一種染色渲染所有的基數(shù)線(xiàn)條:
context.fillStyle?=?COLOR1;
for?(var?i?=?0;?i?<?STRIPES/2;?i++)?{
????context.fillRect((i*2)?*?GAP,?0,?GAP,?480);
}
context.fillStyle?=?COLOR2;
for?(var?i?=?0;?i?<?STRIPES/2;?i++)?{
????context.fillRect((i*2+1)?*?GAP,?0,?GAP,?480);
}??
1.4 只重繪變化部分而不是全部重繪
4.RENDER SCREEN DIFFERENCES ONLY, NOT THE?WHOLE ?NEW STATE
????????在屏幕上繪制較少的東西要比繪制大量的東西節(jié)省資源。重繪時(shí)如果只有少量的差異你可以通過(guò)僅僅重繪差異部分來(lái)獲得顯著的性能提升。換句話(huà)說(shuō),不要在重繪前清除整個(gè)畫(huà)布。:
context.fillRect(0,?0,?canvas.width,?canvas.height);
????????跟蹤已繪制部分的邊界框,僅僅清理這個(gè)邊界之內(nèi)的東西:
context.fillRect(last.x,?last.y,?last.width,?last.height);??
????????如果您對(duì)計(jì)算機(jī)圖形學(xué)比較熟悉,你或許應(yīng)該知道這項(xiàng)技術(shù)也叫做“redraw technique”,這項(xiàng)技術(shù)會(huì)保存前一個(gè)渲染操作的邊界框,下一次繪制前僅僅清理這一部分的內(nèi)容。這項(xiàng)技術(shù)也適用于基于像素的渲染環(huán)境。這篇名為JavaScript NIntendo emulator?tallk的文章說(shuō)明了這一點(diǎn)。
1.5 使用多圖層繪制復(fù)雜場(chǎng)景
5.USE MUTIPLE LAYERED?CANVASES FOR COMPLEX SCENES
????????我們前邊提到過(guò),繪制一副較大的圖片代價(jià)是很高昂的因此我們應(yīng)盡可能的避免。除了前邊講到的利用另外得不可見(jiàn)的canvas進(jìn)行預(yù)渲染外,我們也可以疊在一起的多層canvas。利用前景的透明度,我們可以在渲染時(shí)依靠GPU整合不同的alpha值。你可以像如下這么設(shè)置,兩個(gè)絕對(duì)定位的canvas一個(gè)在另一個(gè)的上邊:

????????相對(duì)于僅僅有一個(gè)canvas的情況來(lái)講,這個(gè)方法的優(yōu)勢(shì)在于,當(dāng)我們需要繪制或者清理前景canvas時(shí),我們不需要每次都修改背景canvas。如果你的游戲或者多媒體應(yīng)用可以分成前景和背景這樣的情況,那么請(qǐng)考慮分別渲染前景和背景來(lái)獲取顯著的性能提升。
????????你可以用相較慢的速度(相對(duì)于前景)來(lái)渲染背景,這樣便可利用人眼的一些視覺(jué)特性達(dá)到一定程度的立體感,這樣會(huì)更吸引用戶(hù)的眼球。比如,你可以在每一幀中渲染前景而僅僅每N幀才渲染背景。
????????注意,這個(gè)方法也可以推廣到包含更多canvas曾的復(fù)合canvas。如果你的應(yīng)用利用更多的曾會(huì)運(yùn)行的更好時(shí)請(qǐng)利用這種方法。
1.6 減少使用陰影效果
6.AVOID SHADOWBLUR
????????跟其他很多繪圖環(huán)境一樣,HTML5 canvas允許開(kāi)發(fā)者對(duì)繪圖基元使用陰影效果,然而,這項(xiàng)操作是相當(dāng)耗費(fèi)資源的。
context.shadowOffsetX?=?5;
context.shadowOffsetY?=?5;
context.shadowBlur?=?4;
context.shadowColor?=?'rgba(255,?0,?0,?0.5)';
context.fillRect(20,?20,?150,?100);??
1.7 熟悉多種重繪方法
7.KNOW VARIOUS WAYS TO?CLEAR THE CANVAS
????????因?yàn)镠TML5 canvas 是一種即時(shí)模式(immediate mode)的繪圖范式(drawing paradigm),因此場(chǎng)景在每一幀都必需重繪。正因?yàn)榇?,清楚canvas的操作對(duì)于 HTML5 應(yīng)用或者游戲來(lái)說(shuō)有著根本的重要性。
????????正如在 避免 canvas 狀態(tài)變化的一節(jié)中提到的,清楚整個(gè)canvas的操作往往是不可取的。如果你必須這樣做的話(huà)有兩種方法可供選擇:調(diào)用
context.clearRect(0,?0,?width,?height)
????????或者使用 canvas特定的一個(gè)技巧
canvas.width?=?canvas.width
????????在書(shū)寫(xiě)本文的時(shí)候,cleaRect方法普遍優(yōu)越于重置canvas寬度的方法。但是,在某些情況下,在Chrome14中使用重置canvas寬度的技巧要比clearRect方法快很多(jsperf):
????????請(qǐng)謹(jǐn)慎使用這一技巧,因?yàn)樗艽蟪潭壬弦蕾?lài)于底層的canvas實(shí)現(xiàn),因此很容易發(fā)生變化,欲了解更多信息請(qǐng)參見(jiàn) Simon Sarris 的關(guān)于清除畫(huà)布的文章。
1.8 減少浮點(diǎn)坐標(biāo)繪制
8. AVOID FLOATING POINT?COORDINATES
????????HTML5 canvas 支持子像素渲染(sub-pixel rendering),而且沒(méi)有辦法關(guān)閉這一功能。如果你繪制非整數(shù)坐標(biāo)他會(huì)自動(dòng)使用抗鋸齒失真以使邊緣平滑。以下是相應(yīng)的視覺(jué)效果(參見(jiàn)Seb Lee-Delisle的關(guān)于子像素畫(huà)布性能的文章)
? ? ? ? 如果平滑的精靈并非您期望的效果,那么使用 Math.floor方法或者M(jìn)ath.round方法將你的浮點(diǎn)坐標(biāo)轉(zhuǎn)換成整數(shù)坐標(biāo)將大大提高運(yùn)行速度(jsperf):
????????為使浮點(diǎn)坐標(biāo)抓換為整數(shù)坐標(biāo)你可以使用許多聰明的技巧,其中性能最優(yōu)越的方法莫過(guò)于將數(shù)值加0.5然后對(duì)所得結(jié)果進(jìn)行移位運(yùn)算以消除小數(shù)部分。
//?With?a?bitwise?or.
rounded?=?(0.5?+?somenum)?|?0;
//?A?double?bitwise?not.
rounded?=?~~?(0.5?+?somenum);
//?Finally,?a?left?bitwise?shift.
rounded?=?(0.5?+?somenum)?<<?0;
????????兩種方法性能對(duì)比如下(jsperf):
1.9 盡量使用requeatAnimationFrame方法執(zhí)行動(dòng)畫(huà)
9.OPTIMIZE YOUR?ANIMATIONS WITH ‘REQUESTANIMATIONFRAME’
????????相對(duì)較新的 requeatAnimationFrame API是在瀏覽器中實(shí)現(xiàn)交互式應(yīng)用的推薦標(biāo)準(zhǔn)。與傳統(tǒng)的以固定頻率命令瀏覽器進(jìn)行渲染不同,該方法可以更友善的對(duì)待瀏覽器,它會(huì)在瀏覽器可用的時(shí)候使其來(lái)渲染。這樣帶來(lái)的另外一個(gè)好處是當(dāng)頁(yè)面不可見(jiàn)的時(shí)候,它會(huì)很聰明的停止渲染。
????????requestAnimationFrame調(diào)用的目標(biāo)是以60幀每秒的速度來(lái)調(diào)用,但是他并不能保證做到。所以你要跟蹤從上一次調(diào)用導(dǎo)線(xiàn)在共花了多長(zhǎng)時(shí)間。這看起來(lái)可能如下所示:
var?x?=?100;
var?y?=?100;
var?lastRender?=?new?Date();
function?render()?{
? ??var?delta?=?newDate()?-?lastRender;
????x?+=?delta;
????y?+=?delta;
????context.fillRect(x,?y,?W,?H);
????requestAnimationFrame(render);
}
render();?
????????注意requestAnimationFrame不僅僅適用于canvas 還適用于諸如WebGL的渲染技術(shù)。
????????在書(shū)寫(xiě)本文時(shí),這個(gè)API僅僅適用于Chrome,Safari以及Firefox,所以你應(yīng)該使用這一代碼片段
1.10 職責(zé)分離
????????與渲染無(wú)關(guān)的計(jì)算交給worker,復(fù)雜的計(jì)算交給引擎(自己寫(xiě),或者用開(kāi)源的),比如3D、物理 。緩存load好的圖片,canvas上畫(huà)canvas,而不是畫(huà)image。
2 圖層優(yōu)化
2.1 多層半透明優(yōu)化處理
2.1.1 范例1——模擬波浪性能優(yōu)化
2.1.1.1 繪制機(jī)制
????????在最近這個(gè)項(xiàng)目中,有一個(gè)模擬波浪的特效,繪制原理是用多層半透明Canvas進(jìn)行疊加:
????1、全局用三層半透明Canvas疊加,各層透明度分別為0.5、0.6、0.8;
????2、每層Canvas中利用濾鏡功能截取上邊沿圖形,而截取的高度則是利用正弦函數(shù)結(jié)合振幅值與頻率進(jìn)行繪制,再通過(guò)一定刷新頻率將繪圖刷新以及平移,以形成動(dòng)態(tài)效果;
????3、各層的振幅與頻率不同,但刷新頻率一致,故各層疊加在一起后即形成三道波浪圖形;
????結(jié)語(yǔ):
????????這樣做出來(lái)的效果比較逼真,但是性能損壞很大,在iPhone4s上,因?yàn)槠聊讳秩鹃_(kāi)銷(xiāo)太大,已經(jīng)導(dǎo)致界面響應(yīng)事件失效了。因?yàn)槠聊焕L制時(shí),每個(gè)像素點(diǎn)上的顏色計(jì)算,需要集合三層Canvas的透明度來(lái)計(jì)算,非常損耗CPU性能。
2.1.1.2 代碼
2.1.1.2.1 HTML代碼:

2.1.1.2.2 CSS代碼
.control_panel_wrap .filter_info_wrap .controlPanel_animation_wrap .waves_wrap #waves1
{
??? position: absolute;
??? top: 100px;
???filter: alpha(Opacity=80);
??? opacity:.8
}
.control_panel_wrap .filter_info_wrap .controlPanel_animation_wrap .waves_wrap #waves2
{
??? position: absolute;
??? top:100px;
???filter: alpha(Opacity=70);
??? opacity:.7
}
.control_panel_wrap .filter_info_wrap .controlPanel_animation_wrap .waves_wrap #waves3
{
??? position: absolute;
??? top:100px;
???filter: alpha(Opacity=50);
??? opacity:.5
}
2.1.1.2.3 JS代碼:
function createWave(canvasId, amplitude, frequency) {
??? if (typeof(Humble)== 'undefined') window.Humble = {};
??? Humble.Trig = {};
??? Humble.Trig.init =init;
??? var canvas, context, height, width, xAxis, yAxis, draw;
??? //設(shè)置振幅和頻率
??? var amplitude = amplitude || 10;
??? var frequency = frequency || 0.2;
??? /**
???? * Init function.
???? *
???? * Initialize variables and begin the animation.
???? */
??? function init() {
????? canvas = document.getElementById(canvasId);
????? canvas.width = 320;
????? canvas.height = 120;//320
????? context = canvas.getContext("2d");
????? height = canvas.height;
????? width = canvas.width;
????? xAxis =Math.floor(height/2);
????? yAxis = 0;
????? context.save();
????? draw();
??? }
??? /**
???? * Draw animation function.
???? *
???? * This function draws one frame of the animation, waits 20ms, and then calls
???? * itself again.
???? */
??? draw = function (){
????? // Clear the canvas
?????context.clearRect(0, 0, width, height);
????? // Set styles for animated graphics
????? var waveGradient= context.createLinearGradient(0, 0, 0, canvas.height);
????? waveGradient.addColorStop(0, 'rgba(255,255,255,1)');
? ? ? waveGradient.addColorStop(1, 'rgba(255,255,255,0)');
????? context.save();
? ? ? context.strokeStyle = waveGradient;
? ? ? context.fillStyle = waveGradient;
? ? ? context.lineWidth = 1;
????? // Draw the sine curve at time draw.t, as well as the circle.
? ? ? context.beginPath();
? ? ? drawSine(draw.t);
? ? ? context.stroke();
? ? ? ?// Restore original styles
? ? ? context.restore();
????? // Update the time and draw again
????? draw.seconds = draw.seconds - .007;
????? draw.t = draw.seconds*Math.PI;
????? setTimeout(draw,35);
??? };
??? draw.seconds = 0;
??? draw.t = 0;
??? /**
???? * Function to draw sine
???? *
???? * The sine curve is drawn in 10px segments starting at the origin.
???? */
??? function drawSine(t) {
????? // Set the initial x and y, starting at 0,0 and translating to the origin on
????? // the canvas.
????? var x = t;
????? var y = Math.sin(x);
?????context.moveTo(yAxis, amplitude*y+xAxis);
????? // Loop to draw segments
????? for (i = yAxis; i <= width; i += 10) {
? ? ? ? ?x =t+(-yAxis+i)/amplitude*frequency;
? ? ? ? ?y =Math.sin(x);
? ? ? ? context.lineTo(i, amplitude*y+xAxis);
????? }
? ? ? context.lineTo(canvas.width, canvas.height);
? ? ? context.lineTo(0, canvas.height);
//?????context.stroke();
? ? ? ?context.fill();
??? }
??? Humble.Trig.init()
? }
?? //測(cè)試到底是哪個(gè)效果好計(jì)算資源
? //生成三條波浪,比較耗資源,至少有50%的幾率造成事件阻塞
? createWave('waves1');
? createWave('waves2',15, 1/11);
? createWave('waves3',20, 1/4);
2.1.1.3 優(yōu)化措施1——減少不變部分范圍:
? ? ? ? 特效其實(shí)只是要求上邊沿有濾鏡效果,變動(dòng)部分相對(duì)較少,下半部分都是不會(huì)變動(dòng),故考慮縮減Canvas高度,將下半部分分離出來(lái)做純色出來(lái)或者只貼一層半透明Canvas,透明度值直接計(jì)算出來(lái)。
? ? ? ? 本APP中簡(jiǎn)單處理,將原來(lái)320*320尺寸的Canvas縮減為320*120,再將Canvas下移200px,直接縮減疊加層范圍,較少渲染計(jì)算范圍,提高APP性能。
2.1.1.4 優(yōu)化措施2——直接計(jì)算半透明度
性能優(yōu)化思路:
? ? ? ? 三個(gè)半透明層疊加,是否可以在一層中處理,對(duì)于不同坐標(biāo)值,直接計(jì)算出顏色值,然后渲染,變量包括時(shí)間t、橫坐標(biāo)x,計(jì)算分支判斷依據(jù)是縱坐標(biāo)y。
3 參考鏈接
Immediate mode?vs.?retained?mode.
Other HTML5 Rocks?canvas articles.
The?Canvas section?ofDive into HTML5.
JSPerf?lets developers create JS performance tests.
Browserscope?stores browser performance data.
JSPref view, which renders JSPerf tests as charts.
Simon's blog post?on clearing the canvas.
Sebastian's blog post?on sub-pixel rendering performance.
Paul's blog post?on using the?requestAnimationFrame.
Ben's talk?about optimizing a JS NES emulator.