總原則:
????????在移動APP中,因為手機硬件性能有限,其實不宜做太多特效,應該往簡潔突出重點的方向考慮。
1 性能建議
英文引文地址:
http://www.html5rocks.com/en/tutorials/canvas/performance/
提高HTML5 canvas性能的幾種方法!
http://blog.csdn.net/zyz511919766/article/details/7401792
1.1 預渲染
1.PRE-RENDER TO AN OFF-SCREEN CANVAS
????????預渲染即在一個或者多個臨時的不會在屏幕上顯示的canvas中渲染臨時的圖像,然后再把這些不可見的canvas作為圖像渲染到可見的canvas中。對于計算機圖形學比較熟悉的朋友應該都知道,這個技術也被稱做display list。
????????沒有預渲染的情況:
//?canvas,?context?are?defined
function?render()?{
????drawMario(context);
????requestAnimationFrame(render);
}
????????預渲染的情況:
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);
}??
????????關于requestAnimationFrame的使用方法將在后續部分做詳細的講述。當渲染操作(例如上例中的drawmario)開銷很大時該方法將非常有效。其中很耗資源的文本渲染操作就是一個很好的例子。
????????要確保臨時的canvas恰好適應你準備渲染的圖片的大小,否則過大的canvas會導致我們獲取的性能提升被將一個較大的畫布復制到另外一個畫布的操作帶來的性能損失所抵消掉。
????????上述的測試用例中緊湊的canvas相當的小:
can2.width?=?100;
can2.height?=?40;
????????如下寬松的canvas將導致糟糕的性能:
can3.width?=?300;??
1.2 多條指令一次載入
2.BATCH CANVAS CALLS TOGETHER
????????因為繪圖是一個代價昂貴的操作,因此,用一個長的指令集載入將繪圖狀態機載入,然后再一股腦的全部寫入到video緩沖區。這樣會會更佳有效率。
????????例如,當需要畫對條線條時先創建一條包含所有線條的路經然后用一個draw調用將比分別單獨的畫每一條線條要高效的多:
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();
}
????????通過繪制一個包含多條線條的路徑我們可以獲得更好的性能:
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();
????????這個方法也適用于HTML5 canvas。比如,當我們畫一條復雜的路徑時,將所有的點放到路徑中會比分別單獨的繪制各個部分要高效的多(jsperf):
????????然而,需要注意的是,對于canvas來說存在一個重要的例外情況:若欲繪制的對象的部件中含有小的邊界框(例如,垂直的線條或者水平的線條),那么單獨的渲染這些線條或許會更加有效(jsperf)
1.3 避免不必要的狀態切換
3.AVOID UNNECESSARY CANVAS STATE CHANGES
????????HTML5 canvas元素是在一個狀態機之上實現的。狀態機可以跟蹤諸如fill、stroke-style以及組成當前路徑的previous points等等。在試圖優化繪圖性能時,我們往往將注意力只放在圖形渲染上。實際上,操縱狀態機也會導致性能上的開銷。
????????例如,如果你使用多種填充色來渲染一個場景,按照不同的顏色分別渲染要比通過canvas上的布局來進行渲染要更加節省資源。為了渲染一副條紋的圖案,你可以這樣渲染:用一種顏色渲染一條線條,然后改變顏色,渲染下一條線條,如此反復:
for?(var?i?=?0;?i?<?STRIPES;?i++)?{
? context.fillStyle?=?(i?%?2???COLOR1?:?COLOR2);
? context.fillRect(i?*?GAP,?0,?GAP,?480);
}??
????????也可以先用一種顏色渲染所有的偶數線條再用另外一種染色渲染所有的基數線條:
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
????????在屏幕上繪制較少的東西要比繪制大量的東西節省資源。重繪時如果只有少量的差異你可以通過僅僅重繪差異部分來獲得顯著的性能提升。換句話說,不要在重繪前清除整個畫布。:
context.fillRect(0,?0,?canvas.width,?canvas.height);
????????跟蹤已繪制部分的邊界框,僅僅清理這個邊界之內的東西:
context.fillRect(last.x,?last.y,?last.width,?last.height);??
????????如果您對計算機圖形學比較熟悉,你或許應該知道這項技術也叫做“redraw technique”,這項技術會保存前一個渲染操作的邊界框,下一次繪制前僅僅清理這一部分的內容。這項技術也適用于基于像素的渲染環境。這篇名為JavaScript NIntendo emulator?tallk的文章說明了這一點。
1.5 使用多圖層繪制復雜場景
5.USE MUTIPLE LAYERED?CANVASES FOR COMPLEX SCENES
????????我們前邊提到過,繪制一副較大的圖片代價是很高昂的因此我們應盡可能的避免。除了前邊講到的利用另外得不可見的canvas進行預渲染外,我們也可以疊在一起的多層canvas。利用前景的透明度,我們可以在渲染時依靠GPU整合不同的alpha值。你可以像如下這么設置,兩個絕對定位的canvas一個在另一個的上邊:
????????相對于僅僅有一個canvas的情況來講,這個方法的優勢在于,當我們需要繪制或者清理前景canvas時,我們不需要每次都修改背景canvas。如果你的游戲或者多媒體應用可以分成前景和背景這樣的情況,那么請考慮分別渲染前景和背景來獲取顯著的性能提升。
????????你可以用相較慢的速度(相對于前景)來渲染背景,這樣便可利用人眼的一些視覺特性達到一定程度的立體感,這樣會更吸引用戶的眼球。比如,你可以在每一幀中渲染前景而僅僅每N幀才渲染背景。
????????注意,這個方法也可以推廣到包含更多canvas曾的復合canvas。如果你的應用利用更多的曾會運行的更好時請利用這種方法。
1.6 減少使用陰影效果
6.AVOID SHADOWBLUR
????????跟其他很多繪圖環境一樣,HTML5 canvas允許開發者對繪圖基元使用陰影效果,然而,這項操作是相當耗費資源的。
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
????????因為HTML5 canvas 是一種即時模式(immediate mode)的繪圖范式(drawing paradigm),因此場景在每一幀都必需重繪。正因為此,清楚canvas的操作對于 HTML5 應用或者游戲來說有著根本的重要性。
????????正如在 避免 canvas 狀態變化的一節中提到的,清楚整個canvas的操作往往是不可取的。如果你必須這樣做的話有兩種方法可供選擇:調用
context.clearRect(0,?0,?width,?height)
????????或者使用 canvas特定的一個技巧
canvas.width?=?canvas.width
????????在書寫本文的時候,cleaRect方法普遍優越于重置canvas寬度的方法。但是,在某些情況下,在Chrome14中使用重置canvas寬度的技巧要比clearRect方法快很多(jsperf):
????????請謹慎使用這一技巧,因為它很大程度上依賴于底層的canvas實現,因此很容易發生變化,欲了解更多信息請參見 Simon Sarris 的關于清除畫布的文章。
1.8 減少浮點坐標繪制
8. AVOID FLOATING POINT?COORDINATES
????????HTML5 canvas 支持子像素渲染(sub-pixel rendering),而且沒有辦法關閉這一功能。如果你繪制非整數坐標他會自動使用抗鋸齒失真以使邊緣平滑。以下是相應的視覺效果(參見Seb Lee-Delisle的關于子像素畫布性能的文章)
? ? ? ? 如果平滑的精靈并非您期望的效果,那么使用 Math.floor方法或者Math.round方法將你的浮點坐標轉換成整數坐標將大大提高運行速度(jsperf):
????????為使浮點坐標抓換為整數坐標你可以使用許多聰明的技巧,其中性能最優越的方法莫過于將數值加0.5然后對所得結果進行移位運算以消除小數部分。
//?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;
????????兩種方法性能對比如下(jsperf):
1.9 盡量使用requeatAnimationFrame方法執行動畫
9.OPTIMIZE YOUR?ANIMATIONS WITH ‘REQUESTANIMATIONFRAME’
????????相對較新的 requeatAnimationFrame API是在瀏覽器中實現交互式應用的推薦標準。與傳統的以固定頻率命令瀏覽器進行渲染不同,該方法可以更友善的對待瀏覽器,它會在瀏覽器可用的時候使其來渲染。這樣帶來的另外一個好處是當頁面不可見的時候,它會很聰明的停止渲染。
????????requestAnimationFrame調用的目標是以60幀每秒的速度來調用,但是他并不能保證做到。所以你要跟蹤從上一次調用導線在共花了多長時間。這看起來可能如下所示:
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的渲染技術。
????????在書寫本文時,這個API僅僅適用于Chrome,Safari以及Firefox,所以你應該使用這一代碼片段
1.10 職責分離
????????與渲染無關的計算交給worker,復雜的計算交給引擎(自己寫,或者用開源的),比如3D、物理 。緩存load好的圖片,canvas上畫canvas,而不是畫image。
2 圖層優化
2.1 多層半透明優化處理
2.1.1 范例1——模擬波浪性能優化
2.1.1.1 繪制機制
????????在最近這個項目中,有一個模擬波浪的特效,繪制原理是用多層半透明Canvas進行疊加:
????1、全局用三層半透明Canvas疊加,各層透明度分別為0.5、0.6、0.8;
????2、每層Canvas中利用濾鏡功能截取上邊沿圖形,而截取的高度則是利用正弦函數結合振幅值與頻率進行繪制,再通過一定刷新頻率將繪圖刷新以及平移,以形成動態效果;
????3、各層的振幅與頻率不同,但刷新頻率一致,故各層疊加在一起后即形成三道波浪圖形;
????結語:
????????這樣做出來的效果比較逼真,但是性能損壞很大,在iPhone4s上,因為屏幕渲染開銷太大,已經導致界面響應事件失效了。因為屏幕繪制時,每個像素點上的顏色計算,需要集合三層Canvas的透明度來計算,非常損耗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;
??? //設置振幅和頻率
??? 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()
? }
?? //測試到底是哪個效果好計算資源
? //生成三條波浪,比較耗資源,至少有50%的幾率造成事件阻塞
? createWave('waves1');
? createWave('waves2',15, 1/11);
? createWave('waves3',20, 1/4);
2.1.1.3 優化措施1——減少不變部分范圍:
? ? ? ? 特效其實只是要求上邊沿有濾鏡效果,變動部分相對較少,下半部分都是不會變動,故考慮縮減Canvas高度,將下半部分分離出來做純色出來或者只貼一層半透明Canvas,透明度值直接計算出來。
? ? ? ? 本APP中簡單處理,將原來320*320尺寸的Canvas縮減為320*120,再將Canvas下移200px,直接縮減疊加層范圍,較少渲染計算范圍,提高APP性能。
2.1.1.4 優化措施2——直接計算半透明度
性能優化思路:
? ? ? ? 三個半透明層疊加,是否可以在一層中處理,對于不同坐標值,直接計算出顏色值,然后渲染,變量包括時間t、橫坐標x,計算分支判斷依據是縱坐標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.