阿里巴巴技術文章:使用 HTML5 Canvas 實現移動平臺動畫(游戲)的一些痛點和思路
來自: http://yq.aliyun.com/articles/3165?spm=5176.100239.yqblog1.20.nRIiAk
前言
最近一直在做一些HTML5 Canvas加速方面的事情,HTML5已經出來相當長一段時間,各瀏覽器廠商也基本上已經支持!從目前來看,HTML5的大規模普及還是雷聲大,雨點小;但從長遠來看,HTML5由于其諸多優點,比如本文提到到Canvas元素支持,將會逐漸成為主流,特別是在復雜場景應用中! 本文主要從目前HTML5 Canvas 在實現動畫(游戲)中遇到的部分痛點入手,嘗試提供一些相應的解決辦法和思路!
痛點
-
性能問題:目前性能問題HTML5 Canvas的性能問題在Android中表現得尤為突出
-
耗電,CPU,內存占用等問題:由于動畫,游戲的特殊性,耗電等一直是此類應用中比較突出的問題,并非HTML5 Canvas特有的問題
-
品質問題:由于HTML 5是偏Web的一類應用,因此在使用者的固有印象中,一直認為HTML Canvas只能做一些比較初級的動畫和游戲,比較復雜的一些游戲,往往就覺得HTML Canvas無法勝任,轉而尋求Native的解決方案
-
兼容性/適配性問題:這一方面是由于平臺造成,比如Android平臺的碎片化;另一方面是瀏覽器的分裂實現,造成同一套標準多個不同版本的不同實現,進而造成開發者要兼顧各種各樣的瀏覽器!
-
調試問題:由于移動終端的特殊性,目前在移動終端上調試 JavaScript應用還是比較困難的一件事情!
從不同關注點做一個簡單分類:

思路
針對上述問題,下面提供了一些方法和思路,可以根據不同的場景和應用做參考:
1. 性能
1). JavaScript 語言層面優化
(1). 設計優化
A. 文字繪制使用緩存Canvas
說明:在動畫(游戲)場景中,文字使用一般占比都比較少,但文字的繪制比起圖片等來說,其實是最復雜,也最耗時間的!因此大多游戲如果不是出于有文字交互,或動態文字繪制等需求的話,一般會直接使用位圖來代替!但也有很多場合需要直接繪制文字,對應于Canvas 的Api主要就是fillText! 在這種情況下,如果直接fillText到顯示的Canvas上,會嚴重降低fps!
方法:創建一個不可見的緩沖Canvas,文字首先繪制到此Canvas上,當需要顯示的時候,通過drawImage(canvas...)繪制到顯示的Canvas上,這樣可有效避免直接調用 fillText 帶來的性能降低問題
例子:cocos2d-js,phaser等游戲框架中對文字的處理主要用了此方法
B. 語言層面的設計優化還有很多方式,可以從下面兩個鏈接中找到更多方法:
http://www.cnblogs.com/rhcad/archive/2012/11/17/2774794.html
http://www.cnblogs.com/iamzhanglei/archive/2011/11/29/2267868.html(2). 工具輔助優化
語言層面不僅可以直接從具體的設計上優化,也可以使用輔助工具來幫助排除js性能瓶頸。
2).Runtime層面優化:
(1). 渲染優化
由于各個平臺的實現機制不同,或者同一平臺下(比如Android)下各個版本提供的瀏覽器渲染內核也不盡相同,因此造成HTML5 Canvas在許多情況下渲染性能嚴重降低!鑒于此,許多直接從Runtime層面來解決性能問題的方案也應運而生!并且取得了不錯的效果!比如:
A. 使用GPU硬件加速
方法:直接通過Opengl來實現硬件加速,提高渲染速度,
例子:CocoonJS,Intel XDK等;
2. 耗電
由于動畫(游戲)的特殊性,需要不停渲染顯示,相對于其他應用來說,會消耗比較多的電量。
目前為止,并沒有太多減少耗電量的有效辦法。下面的建議可能會有一些幫助:
(1). 在游戲流暢度不受影響的情況下,盡量降低fps, 由于fps越高,渲染越頻繁,勢必消耗越多的電量;但fps達到60幀/s以后,就已經達到顯卡的渲染能力上限,因此過渡提高fps只會增加耗電量;
3. 提高畫面品質
(1). 提高平滑度
除了畫面的視覺效果外,在動畫或游戲中,比較重要的就是畫面過渡要流暢,沒有生硬感,眼睛不會感覺到刺痛!要改善這些,可以使用下面的方法:
A. 兩個畫面之間補幀的方式
通過一定的補幀算法(比如 easeIn ,easeOut,透明度變化的)等方式,讓前一個畫面以微小的變化不停過渡到另一個畫面,達到改善過渡的效果!這里比較重要的一點就是在以前的javascript動畫實現中,通常通過setTimeout 或setInterval來實現時間控制,推薦使用requestAnimationFrame來控制動畫的播放,可以實現比較精確的時間控制,效率較setTimeout高,下面這篇文章給出了一些動畫的過渡算法例子
http://www.zhangxinxu.com/wordpress/2013/09/css3-animation-requestanimationframe-tween-動畫算法/
(2). 特效:在Runtime支持的情況下,可以使用部分特效效果來提高畫面品質,比如3D
4. 兼容性處理
由于瀏覽器內核分裂和平臺碎片化問題(比如Android),導致Api在各個瀏覽器和Android各個版本下實現不盡一樣,比較典型的比如bind在android 某些版本上沒有實現,請求動畫幀方法requestAnimationFrame在各瀏覽器內核上的實現不同等。這給開發者帶來很大煩惱,要針對不同的平臺和瀏覽器做不同的實現。對于此問題,可以自己根據不同版本做不同的判斷來處理,更好的處理版本可以使用一種叫做polyfills的方法,通過這種方式,可以不用擔心自己所使用的api在不同的平臺上找不到。polyfills這個術語比較拗口,詳細可以看一看下面這篇文章,對polyfills做了比較全面的總結!
https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills下面是一個針對requestAnimationFrame和bind的polyfill實現例子:
// Function.bind
// source: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
// window.requestAnimationFrame
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
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, element) {
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);
};
}());</code></pre>
5. 調試
目前在移動平臺上調試javascript相對PC環境來說,相對比較麻煩,下文總結了目前可以使用的一些調試方式,可以參考:
http://blog.csdn.net/alexwang1983/article/details/16882163
該文章來自于阿里巴巴技術協會( ATA )
作者:許凡
</code></div>