キャンバスの最適化

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

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

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

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

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

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

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

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

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

js
ctx.drawImage(myImage, 0.3, 0.5);

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

drawImage で画像の拡大縮小を行わない

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

複雑なシーンでは複数レイヤーのキャンバスを使用する

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

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

html
<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 プロパティを使用し、キャンバスの下に配置することで描画することができます。これにより、大きな画像を毎回キャンバスに描画する処理を避けます。

CSS 座標変換を使用してキャンバスを拡大縮小する

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

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

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

stage.style.transformOrigin = "0 0"; // 左上から拡大
stage.style.transform = `scale(${scaleToFit})`;

透過をやめる

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

js
const ctx = canvas.getContext("2d", { alpha: false });

高解像度のディスプレイでの変倍

高解像度のディスプレイでは、キャンバスのアイテムがぼやけて見えることがあります。多くの解決策がありますが、まず簡単な方法は、キャンバスの属性、スタイル、およびコンテキストのスケールを使用してキャンバスのサイズを同時に変倍することです。

js
// DPR とキャンバスの大きさを取得
const dpr = window.devicePixelRatio;
const rect = canvas.getBoundingClientRect();

// キャンバスの「実際の」大きさを設定
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;

// 正しい描画操作を保証するためにコンテキストの変倍
ctx.scale(dpr, dpr);

// キャンバスの「描画される」大きさを設定
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;

その他の TIPS

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

関連情報