canvas の最適化

<canvas> 要素は、ウェブで 2D グラフィックスを描画するためにもっとも広く使用されているツールのひとつです。しかし、ウェブサイトやアプリが Canvas API の限界付近まで使用するようになって、パフォーマンスが悪化するようになりました。この記事では、 canvas 要素の使用を最適化して、グラフィックを確実に改善するための提案を行います。

パフォーマンスに関する TIPS

キャンバスのパフォーマンスを向上させるための TIPS 集を以下に掲載します。

同様のプリミティブや繰り返し使用するオブジェクトをオフスクリーン canvas で事前にレンダリングする

アニメーションフレーム毎に同じ描画操作を繰り返していることに気づいたら、あらかじめオフスクリーンキャンバスに描画しておくことを検討しましょう。そして、必要な時に本来のキャンバスにオフスクリーン画像を、最初の場所で生成したときのステップなしで描画することができます。

myCanvas.offscreenCanvas = document.createElement('canvas');
myCanvas.offscreenCanvas.width = myCanvas.width;
myCanvas.offscreenCanvas.height = myCanvas.height;

myCanvas.getContext('2d').drawImage(myCanvas.offScreenCanvas, 0, 0);

浮動小数点数値の座標を避けて整数を使用する

canvas で整数以外の値を使用してオブジェクトを描画すると、サブピクセルレンダリングを実行します。

ctx.drawImage(myImage, 0.3, 0.5);

これはアンチエイリアス効果を生成するために、ブラウザーに追加の計算処理を強制します。これを避けるために、たとえば drawImage() を呼び出す際に Math.floor() を使用して、すべての座標で端数処理を行ってください。

drawImage で画像のスケーリングを行わない

drawImage() でいつも画像のスケーリング処理を行うのではなく、さまざまなサイズの画像をオフスクリーン canvas でキャッシュしてください。

複雑なシーンでは複数レイヤーの canvas を使用する

アプリケーションでは、一部のオブジェクトは頻繁に動かしたり変更したりする必要があるのに対し、他のものは比較的静止していることが分かるかもしれません。この場合に可能な最適化は、複数の <canvas> 要素を使用してアイテムをレイヤー化することです。

例えば、 UI があるゲームが最上位にあり、中間にゲームプレイの動作があり、最下位に静止した背景があるとします。この場合、ゲームを3つの <canvas> レイヤーに分割することができます。 UI はユーザーの入力のみに基づいて変化し、ゲームプレイレイヤーはフレーム毎に変化し、背景は基本的に変化しないままでいます。

<div id="stage">
  <canvas id="ui-layer" width="480" height="320"></canvas>
  <canvas id="game-layer" width="480" height="320"></canvas>
  <canvas id="background-layer" width="480" height="320"></canvas>
</div>
 
<style>
  #stage {
    width: 480px;
    height: 320px;
    position: relative;
    border: 2px solid black;
  }

  canvas { position: absolute; }
  #ui-layer { z-index: 3; }
  #game-layer { z-index: 2; }
  #background-layer { z-index: 1; }
</style>

大きな背景画像に CSS を使用する

静止した背景画像がある場合は、ただの <div> に CSS の background プロパティを使用し、 canvas の下に配置することで描画することができます。これにより、大きな画像を毎回 canvas に描画する処理を避けます。

CSS transforms を使用して canvas をスケーリングする

CSS 変形 は、 GPU を使用しますのでより高速です。もっともよいのは拡大縮小しないことですが、そうでなければ大きな canvas を縮小するよりも小さな canvas を拡大したほうが良好です。

var scaleX = window.innerWidth / canvas.width;
var scaleY = window.innerHeight / canvas.height;

var scaleToFit = Math.min(scaleX, scaleY);
var scaleToCover = Math.max(scaleX, scaleY);

stage.style.transformOrigin = '0 0'; //scale from top left
stage.style.transform = 'scale(' + scaleToFit + ')';

透過をやめる

アプリケーションが canvas を使用していて背後のものを透過させる必要がない場合は、 HTMLCanvasElement.getContext() で描画コンテキストを生成する際に alpha オプションを false に設定しましょう。この情報を使用してブラウザーが描画を最適化する可能性があります。

var ctx = canvas.getContext('2d', { alpha: false });

その他の TIPS

  • canvas の呼び出しをひとまとめにします。たとえば、複数に分割した線分ではなくポリラインを描画します。
  • 不必要な canvas の状態変更を避けます。
  • 新しい状態の全体を描画せずに、スクリーンの差分だけを描画します。
  • 可能な限り shadowBlur プロパティを避けます。
  • 可能な限り テキストレンダリング を避けます。
  • canvas をクリアーする別の方法を試します (clearRect()fillRect() 対 canvas のリサイズ)
  • アニメーションで window.setInterval() の代わりに window.requestAnimationFrame() を使用します。
  • 高負荷な物理演算ライブラリーに注意してください。

関連情報