OffscreenCanvas - ウェブワーカーを使用してキャンバスの処理を高速化

Tim Dresser

Canvas は、あらゆる種類のグラフィックを画面に描画する一般的な方法であり、WebGL の世界へのエントリ ポイントです。シェイプや画像の描画、アニメーションの実行、動画コンテンツの表示と処理に使用できます。メディアリッチなウェブ アプリケーションやオンライン ゲームで美しいユーザー エクスペリエンスを実現するために使用されます。

スクリプト可能であるため、キャンバスに描画されるコンテンツを JavaScript などのプログラムで作成できます。これにより、キャンバスは非常に柔軟性の高いものになります。

一方、最新のウェブサイトでは、スクリプトの実行がユーザーの応答性の問題の最も一般的な原因の一つとなっています。キャンバス ロジックとレンダリングはユーザー操作と同じスレッドで行われるため、アニメーションに関連する(場合によっては負荷の高い)計算がアプリの実際のパフォーマンスとユーザーが認識するパフォーマンスに悪影響を及ぼす可能性があります。

幸い、OffscreenCanvas はそのような脅威に対処するためのものです。

対応ブラウザ

  • Chrome: 69.
  • Edge: 79.
  • Firefox: 105.
  • Safari: 16.4。

ソース

以前は、キャンバス描画機能は <canvas> 要素に関連付けられていました。つまり、DOM に直接依存していました。OffscreenCanvas は、名前が示すように、DOM と Canvas API を画面外に移動することで、DOM と Canvas API を分離します。

この分離により、OffscreenCanvas のレンダリングは DOM から完全に分離されるため、両者の間に同期がないため、通常のキャンバスよりも速度が向上します。

さらに、DOM が使用できない場合でも、Web Worker で使用できます。これにより、さまざまな興味深いユースケースが可能になります。

ワーカーで OffscreenCanvas を使用する

ワーカーは、ウェブ版のスレッドです。ワーカーを使用すると、タスクをバックグラウンドで実行できます。

スクリプトの一部をワーカーに移動すると、ユーザーにとって重要なタスクをメインスレッドで実行するためのアプリのヘッドルームが広がります。OffscreenCanvas がなければ、利用可能な DOM がないため、Worker で Canvas API を使用する方法はありませんでした。

OffscreenCanvas は DOM に依存しないため、使用できます。次の例では、OffscreenCanvas を使用してワーカーでグラデーション カラーを計算します。

// file: worker.js
function getGradientColor(percent) {
  const canvas = new OffscreenCanvas(100, 1);
  const ctx = canvas.getContext('2d');
  const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
  gradient.addColorStop(0, 'red');
  gradient.addColorStop(1, 'blue');
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, ctx.canvas.width, 1);
  const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
  const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
  return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}

getGradientColor(40);  // rgba(152, 0, 104, 255 )

メインスレッドのブロックを解除する

負荷の高い計算をワーカーに移動すると、メインスレッドで大幅にリソースを解放できます。transferControlToOffscreen メソッドを使用して、通常のキャンバスを OffscreenCanvas インスタンスにミラーリングします。OffscreenCanvas に適用されたオペレーションは、ソース キャンバスに自動的にレンダリングされます。

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({canvas: offscreen}, [offscreen]);

次の例では、色テーマが変更されたときに負荷の高い計算が行われます。高速なデスクトップでも数ミリ秒かかります。アニメーションは、メインスレッドまたはワーカーで実行できます。メインスレッドの場合、負荷の高いタスクの実行中はボタンを操作できません。スレッドがブロックされているためです。ワーカーの場合、UI の応答性に影響はありません。

デモ

逆の場合も同様で、ビジー状態のメインスレッドがワーカーで実行されているアニメーションに影響を与えることはありません。この機能を使用すると、次のデモに示すように、視覚的なジャンクを回避し、メインスレッドのトラフィックがあるにもかかわらず、スムーズなアニメーションを保証できます。

デモ

通常のキャンバスの場合、メインスレッドが人為的に過負荷になるとアニメーションが停止しますが、ワーカーベースの OffscreenCanvas はスムーズに再生されます。

OffscreenCanvas API は通常の Canvas 要素と基本的に互換性があるため、プログレッシブ エンハンスメントとして使用できます。また、市販の主要なグラフィック ライブラリでも使用できます。

たとえば、特徴検出を行い、利用可能な場合は、レンダラ コンストラクタでキャンバス オプションを指定して、Three.js で使用できます。

const canvasEl = document.querySelector('canvas');
const canvas =
  'OffscreenCanvas' in window
    ? canvasEl.transferControlToOffscreen()
    : canvasEl;
canvas.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});

注意すべき点は、Three.js ではキャンバスに style.width プロパティと style.height プロパティが存在することが想定されていることです。OffscreenCanvas は DOM から完全に分離されているため、この値はないため、スタブを作成するか、これらの値を元のキャンバスのサイズに関連付けるロジックを用意して、自分で指定する必要があります。

次のコードは、ワーカーで基本的な Three.js アニメーションを実行する方法を示しています。

デモ

DOM 関連の API の中には、ワーカーですぐに使用できないものもあります。テクスチャなどの高度な Three.js 機能を使用したい場合は、回避策が必要になる場合があります。これらの機能のテストを始める方法については、Google I/O 2017 の動画をご覧ください。

キャンバスのグラフィック機能を多用している場合は、OffscreenCanvas がアプリのパフォーマンスにプラスの影響を与える可能性があります。キャンバス レンダリング コンテキストをワーカーが利用できるようにすると、ウェブ アプリケーションの並列処理が強化され、マルチコア システムをより効率的に使用できます。

参考情報