web-dev-qa-db-ja.com

HTML5 Canvasのパフォーマンスと最適化のヒント、コツ、コーディングのベストプラクティス

CANVASのベストプラクティスをもっと知っていますか?

このスレッドに、キャンバスのベストプラクティス、パフォーマンスに関するヒント/トリックを知っている、学んだ、オンラインで読んだものを追加してください

Canvasはまだインターネットに非常に新しく、将来見ることができるという兆候がまったくないため、文書化された「ベストプラクティス」や、開発に必要な「知っておくべき」その他の本当に重要なヒントはあまりありません。任意の特定の場所で。このようなものは、あまり知られていないサイトに何度も散らばっています。

人々が知る必要があることはたくさんありますが、それでも学ぶことはたくさんあります。


Canvasを学習している人や、すでにそれをよく知っている人や、HTML5でCanvasを操作するためのベストプラクティスやその他のヒントやコツについて他の人からフィードバックを得たいと思っている人を助けるために、いくつかのことを共有したかった。

私は個人的に非常に有用であるが、開発者が行うには驚くほど珍しいことであることがわかったものから始めたいと思います。

1.コードをインデントする

あなたが他の時と同じように、どんな場合でも、他の言語で。それは他のすべてのためのベストプラクティスであり、複雑なキャンバスアプリでは、いくつかの異なるコンテキストと保存/復元状態を扱うときに物事が少し混乱することがわかりました。言うまでもなく、コードは読みやすく、全体的にすっきりしています。

例えば:

...
// Try to tell me this doesn't make sense to do
ctx.fillStyle = 'red';
ctx.fill();
ctx.save();
    if (thing < 3) {
        // indenting
        ctx.beginPath();
            ctx.arc(2, 6, 11, 0, Math.PI*2, true);
        ctx.closePath();
        ctx.beginPath();
            ctx.moveTo(20, 40);
            ctx.lineTo(10, 200);
            ctx.moveTo(20, 40);
            ctx.lineTo(100, 40);
        ctx.closePath();
        ctx.save();
            ctx.fillStyle = 'blue'
            ctx.fill();
        ctx.restore();
    } else { 
        // no indenting
        ctx.drawImage(img, 0, 0, 200, 200);
        ctx.save();
        ctx.shadowBlur();
        ctx.beginPath();
        ctx.arc(2, 60, 10, 0, Math.PI*2, false);
        ctx.closePath();
        ctx.fillStyle 'green';
        ctx.fill();
        ctx.restore();
    }
ctx.restore();
ctx.drawRect();
ctx.fill();
...

IFステートメントは、このELSEステートメントよりもすぐに読みやすく、わかりやすいものではありませんか?ここで言っていることがわかりますか?これは、開発者が普通のJavaScriptや他の言語を書くときと同じように実践し続けるべき方法だと思います。

SetInterval/setTimeoutの代わりにrequestAnimationFrameを使用します

setIntervalとsetTimeoutは、アニメーションタイマーとして使用することを意図したものではなく、時間遅延後に関数を呼び出すための単なる汎用メソッドです。将来20ミリ秒の間隔を設定しても、関数のキューが実行するよりも長くかかる場合、これらの関数が完了するまでタイマーは作動しません。それはしばらくの間である可能性がありますが、アニメーションが関係する場合は理想的ではありません。 RequestAnimationFrameは、ブラウザにアニメーションが発生していることを伝えるメソッドであるため、それに応じて再描画を最適化できます。また、非アクティブなタブのアニメーションを調整するため、バックグラウンドでモバイルデバイスを開いたままにしておけば、モバイルデバイスのバッテリーを破壊することはありません。

Nicholas Zakasは、非常に詳細で有益な requestAnimationFrameに関する記事 を彼のブログに書いています。ハードで高速な実装手順が必要な場合は、 Paul IrishがrequestAnimationFrame shimを作成しました –これは、私が最近まで作成したすべてのCanvasアプリで使用したものです。

実際に

Joe Lambertは、setTimeoutおよびsetIntervalの代わりにrequestAnimationFrameを使用するよりも優れており、requestIntervalおよびrequestTimeoutと呼ばれる NEWおよび改良されたshim を作成しました。 スクリプトの要点 を表示できます。

実際にx2

すべてのブラウザーがこの仕様に追いついたので、 requestAnimFrame()polyfillへの更新 がありました。これはおそらく、すべてのベンダーをカバーするために使用されるものです。

複数のキャンバスを使用する

@ nicolahibbertCanvasゲームの最適化に関する彼女の投稿 で書いたアニメーションが重いゲームのテクニックは、複数のキャンバスを重ねて使用する方が良いかもしれないと述べています。単一のキャンバスですべてを行うのではなく、別の方法です。ニコラは、「同じキャンバスにあまりにも多くのピクセルを同時に描画すると、フレームレートが床から落ちます。たとえば、ブレイクアウトを考えてください。レンガ、ボール、パドル、パワーアップや武器を描画しようとしています。 、そしてバックグラウンドの各スター–これは機能しません。これらの各命令を順番に実行するのに時間がかかりすぎます。スターフィールドとゲームの残りの部分を別々のキャンバスに分割することにより、適切なフレームレート。"

要素を画面外にレンダリング

Samsung's Olympic Genome Project facebook app を含むいくつかのアプリでこれを行う必要がありました。それが必要かどうかを知り、利用することは非常に便利なことです。読み込み時間を大幅に短縮します。また、画面に画像を読み込むのに時間がかかることがあるため、非常に便利な手法です。

var tmpCanvas = document.createElement('canvas'),
    tmpCtx = tmpCanvas.getContext('2d'),
    img = document.createElement('img');

img.onload = function() {
    tmpCtx.drawImage(thumbImg, 0, 0, 200, 200);
};
img.src = '/some/image/source.png';

イメージのsrcは、ロード後に設定されることに注意してください。これも忘れてはならない重要なことです。画像の読み込みとこれらの一時キャンバスへの描画が完了したら、同じctx.drawImage()を使用してメインキャンバスに描画できますが、最初の引数として画像を配置する代わりに、「tmpCtx.canvas」を使用します一時キャンバスを参照します。

その他のヒント、トリック、およびリソース

Canvasには後方参照があります

2Dコンテキストには、関連付けられたDOM要素への後方参照があります。

var ctx = doc.getElementById('canvas').getContext('2d');
console.log(ctx.canvas);    // HTMLCanvasElement

これについて他の人からもっと聞いてみたいです。会社の新しいセクションを追加するために標準化すべき項目のリストの作成に取り組んでいます フロントエンドコードの標準とベストプラクティス 。私はこれについてできる限り多くのフィードバックをもらいたいです。

49
jaredwilli

領域の再描画

アニメーションに最適なキャンバス最適化手法は、各フレームでクリア/ペイントされるピクセルの量を制限することです。実装する最も簡単な解決策は、キャンバス要素全体をリセットし、すべてを再度描画することですが、これはブラウザが処理するための高価な操作です。

フレーム間でできるだけ多くのピクセルを再利用します。つまり、各フレームの処理に必要なピクセルが少ないほど、プログラムの実行速度が速くなります。たとえば、clearRect(x、y、w、h)メソッドを使用してピクセルを消去する場合、キャンバス全体ではなく、変更されたピクセルのみをクリアして再描画すると非常に有益です。

手続き型スプライト

グラフィックスを手続き的に生成することは、多くの場合、進むべき方法ですが、時にはそれが最も効率的なものではありません。塗りつぶしのある単純な図形を描画する場合は、手続き型で描画するのが最善の方法です。しかし、ストローク、グラデーション塗りつぶし、その他のパフォーマンスに敏感なメイクアップでより詳細なエンティティを描画する場合は、イメージスプライトを使用する方が良いでしょう。

両方の組み合わせで逃げることができます。アプリケーションの起動時に、グラフィカルエンティティを手順的にキャンバスに描画します。その後、同じドロップシャドウ、グラデーション、ストロークを繰り返し生成する代わりに、コピーをペイントすることで同じスプライトを再利用できます。

状態スタックと変換

キャンバスは、回転やスケーリングなどの変換を介して操作できるため、キャンバスの座標系が変更されます。これは、context.save()(現在の状態をスタックにプッシュする)とcontext.restore()(前の状態に戻す)の2つのメソッドが利用可能な状態スタックについて知ることが重要な場所です。これにより、変換を図面に適用し、前の状態に復元して、次の形状が以前の変換の影響を受けないようにすることができます。状態には、塗りつぶしやストロークの色などのプロパティも含まれます。

合成

キャンバスで作業するときに手元にある非常に強力なツールは、とりわけマスキングとレイヤー化を可能にする合成モードです。使用可能な複合モードにはさまざまな種類があり、それらはすべてキャンバスコンテキストのglobalCompositeOperationプロパティを介して設定されます。複合モードは状態スタックプロパティの一部でもあるため、複合操作を適用し、状態をスタックして別のモードを適用し、最初のモードを作成する前の状態に戻すことができます。これは特に便利です。

アンチエイリアシング

サブピクセルの描画を可能にするために、canvasのすべてのブラウザー実装はアンチエイリアシングを使用します(これはHTML5仕様の要件ではないようですが)。鮮明な線を描き、結果がぼやけていることに気づいたい場合は、アンチエイリアスが重要です。これは、ブラウザが画像を実際にそれらのピクセルの間にあるかのように補間するために発生します。その結果、アニメーションははるかに滑らかになります(更新ごとに半ピクセルで移動できます)が、画像がぼやけて見えます。

この問題を回避するには、塗りつぶしまたはストロークを描画するかどうかに応じて、整数値に丸めるか、半ピクセルずつオフセットする必要があります。

DrawImage()のxおよびy位置に整数を使用する

Canvas要素でdrawImageを呼び出す場合、xとyの位置を整数に丸めると、はるかに高速になります。

これはjsperfのテストケースです 整数を使用した場合が小数を使用した場合と比べてどれだけ速いかを示しています。

したがって、レンダリングする前にxとyの位置を整数に丸めてください。

Math.round()より高速

別のjsperfテストが示す Math.round()が必ずしも数値を丸める最速の方法ではないことを示しています。ビット単位のハックを使用すると、実際には組み込みの方法よりも高速であることがわかります。

キャンバススプライトの最適化

キャンバスをクリアする

既存のピクセルのキャンバス全体をクリアするには、通常context.clearRect(x、y、w、h)が使用されますが、別のオプションが利用可能です。キャンバスの幅/高さが設​​定されるたびに、同じ値に繰り返し設定されても、キャンバスはリセットされます。これは、図面が消えるのに気付くので、動的にサイズ設定されたキャンバスで作業するときに知っておくと便利です。

計算分布

Chrome Developer Toolsプロファイラーは、パフォーマンスのボトルネックを見つけるのに非常に役立ちます。アプリケーションによっては、プログラムの一部をリファクタリングしてパフォーマンスを改善し、ブラウザが特定の部分を処理する方法が必要になる場合がありますあなたのコードの。

最適化手法

16
jaredwilli

ここに私のヒントがあります

1)後でキャンバスの状態をリセットするため、canvas.width = canvas.widthの代わりにclearRectを使用してキャンバスをクリアします

2)キャンバスでマウスイベントを使用している場合、次の機能を使用します。これは信頼性が高く、ほとんどの場合に機能します。

/**  returns the xy point where the mouse event was occured. 
 @param ev The event object.
*/
function getXY(ev){
   return getMousePosition(ev, ev.srcElement || ev.originalTarget);
}

 /**  returns the top-left point of the element
       @param elem The element
   */
function getElementPos(elem){
   var obj = elem;
   var top = 0;
   var left = 0;
    while (obj && obj.tagName != "BODY") {
      top += obj.offsetTop-obj.scrollTop;
      left += obj.offsetLeft -obj.scrollLeft ;
      obj = obj.offsetParent;
     }
  return {
    top: top,
    left: left
    };
};

/**  returns the xy point where the mouse event was occured inside an element. 
@param ev The event object.
 @param elem The element
*/
function getMousePosition(evt, elem){
var pageX, pageY;
if(typeof(window.pageYOffset)=='number') {
    pageX=window.pageXOffset;
    pageY=window.pageYOffset;
}else{
    pageX=document.documentElement.scrollLeft;
    pageY=document.documentElement.scrollTop;
}
var mouseX = evt.clientX - getElementPos(elem).left + pageX;
var mouseY = evt.clientY - getElementPos(elem).top + pageY;
return {
    x: mouseX,
    y: mouseY
};
};

3)IE7をサポートする場合は、ExplorerCanvasを使用します

4)キャンバス全体をクリアする代わりに、クリーニングが必要な部分のみをクリアします。パフォーマンスに優れています。

2
Shamaila Tahir

最近Canvasを使用するFacebookアプリを起動しました およびユーザーのFacebookプロフィール情報(収容する必要があるデータの量は、一部の人にとっては膨大です)オリンピック選手は6度の分離タイプのものが好きです。アプリ内でパフォーマンスを向上させるためにできる限りのことをするための広範な努力で、私は多くのことを学びました。

文字通り、数か月、数日を費やして、すでによく知っているコードをリファクタリングし、それが最も最適な方法であると信じていました。

可能な限りDOM要素を使用する

実際、特にIE 8をサポートするアプリを開発する必要がある場合は、ブラウザはCanvasでより集中的に実行するアプリケーションを処理する準備ができていません。これを書いている時点では、DOMはCanvas APIの現在の実装よりも高速です少なくとも、HTML5とCanvasアプリケーションをアニメーション化する非常に複雑な単一ページで作業しているときに、サムスン

Canvasを使用して複雑な作業を行って画像を円に切り取りながら、物事のパフォーマンスを向上させることができました。これはおそらく私たちのやり方に固執しても大丈夫でしょう。

打ち上げの数日前に、別の手法を試してみることにしました。画面に表示されない一時的なキャンバスを作成し、円などに切り取った後に表示されるキャンバスに配置しました。一時キャンバスを配置するために使用していたy座標。

画像を円に切り抜くために、それは単純ですが、CSS3 border-radiusプロパティを使用して、複雑な一連の状態変更よりもはるかに少ない作業で、独創的で創造的でありながら.clip( ) 方法。

それらがDOMに配置されると、画像のアニメーションが発生し、各画像のDOMノードはCanvasの個別のエンティティとしてアニメーション化されます。 CSSを使用してスタイリングを簡単に完全に制御できるもの。

この手法は、このタイプの作業を行うための別の方法にも似ています。これは、キャンバスを1つのコンテキストに描画するのではなく、キャンバスを重ねて重ねるという、非常によく知られています。

2
jaredwilli

昨夜、共有する価値のあるリストに追加したヒントと提案をいくつか紹介します。

  • <canvas>を選択する以上のことを行う必要がない限り、jQueryを含めないでください。

    キャンバスで作成したほぼすべてのことで、それなしでうまくいくことができました

  • 抽象関数の作成およびコードの分離 。機能を外観または初期描画状態から分離します。

    共通機能を可能な限り再利用可能にします。理想的には、共通の機能を含むutilsオブジェクトを作成できるモジュールパターンを使用する必要があります。

  • 意味のある場合は、1文字と2文字の変数名を使用しますx、y、z)。

    Canvasの座標系は、一般に変数として宣言される単一の文字を追加します。要素の一部として、複数のsingle/double変数(dX、dY、aX、aY、vX、vY)の作成につながる可能性があります。

    入力するか略語を入力することをお勧めします。 Word(dirX、accelX、velX)または説明的であると、後で混乱する可能性があります。

  • ゲーム要素を作成するために必要に応じて呼び出すことができるコンストラクター関数を作成します。コンストラクター内にカスタムメソッドとプロパティを追加し、必要な数のカスタムメソッドとプロパティを作成できます。それらはすべて独自のプロパティとメソッドを持ちます。

    私が作成したBallコンストラクタ関数の例:

    // Ball constructor
    var Ball = function(x, y) {
        this.x = x;
        this.y = y;
    
        this.radius = 10;
        this.color = '#fff';
    
        // Direction and min, max x,y
        this.dX = 15;
        this.dY = -15;
    
        this.minX = this.minY = 20 + this.radius;
        this.maxX = this.radius - (canvasWidth - 20);
        this.maxY = this.radius + canvasHeight;
    
        this.draw = function(ctx) {
            ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, twoPI, true);
            ctx.closePath();
            ctx.save();
                ctx.fillStyle = this.color;
                ctx.fill();
            ctx.restore();
        };
    };
    

ボールを作成する

ball = new Ball(centerX, canvasHeight - Paddle.height - 30);
ball.draw(ctx);
  • 作業に適したベースは、3つの関数を作成することです:init()-すべての初期作業を行い、ベース変数とイベントハンドラーなどを設定しますdraw()-ゲームを開始するために1回呼び出され、最初のフレームを描画します変更、または構築が必要な要素の作成を含むゲーム。 update()-draw()の最後に呼び出され、requestAnimFrameを介して呼び出されます。変更する要素のプロパティを更新します。ここで必要なことだけを行います。

  • ループ内で最小限の作業を行い、変化する部品または要素を更新します。ゲーム要素を作成し、他のUIがアニメーションループの外側で機能するようにします。

    アニメーションループは多くの場合、再帰的な関数です。つまり、アニメーション中に各ループをすばやく繰り返し繰り返し呼び出します。

    多くの要素が一度にアニメーション化される場合、まだコンストラクタ関数を使用して要素を作成していない場合は、コンストラクタ内でrequestAnimFrame/setTimeoutを使用する 'timer'メソッドを作成します。任意のアニメーションループ内で、ただしこの要素にのみ効果があります。

    各ゲーム要素に、コンストラクターで独自のタイマー、描画、およびアニメーションメソッドを持たせることができます。

    これを行うと、各要素の制御が完全に分離され、ループは各要素に分割され、自由に開始/停止できるため、1つの大きなアニメーションループはまったく必要ありません。

または別のオプション:

  • Timer()コンストラクター関数を作成します。これを使用して、各アニメーション要素を個別に指定できるため、アニメーションループ内の作業負荷を最小限に抑えることができます。
1
jaredwilli