2018年9月にリリースされたChrome 69からOffscreenCanvas(オフスクリーンキャンバス)が標準で利用できるようになりました。OffscreenCanvasはWeb Workers(ウェブワーカーズ)を使用してWorkerスレッドで描画処理を行える機能です。負荷の高い描画処理をWorkerスレッドに移動することで、メインスレッドの負担が軽くなり、余裕ができます。これによりメインスレッドでスムーズなユーザー操作を実現できるようになり、ユーザー体験の向上が期待できます。本記事ではOffscreenCanvasについて機能と使い方を解説します。
▲ OffscreenCanvasを使用したデモ。OffscreenCanvasを使用すると、Canvasへの描画負荷が高い場合でもスムーズなユーザー操作とCSSアニメーションの再生を実現できる
JavaScriptでマルチスレッドに処理を実行できるWeb Workers
OffscreenCanvasの説明の前に、まずはWeb Workersについておさらいしましょう。JavaScriptは従来シングルスレッドで動作するため、高負荷な処理を行っている最中は他の処理の実行ができず、画面の描画更新やUIの操作ができなくなります。
Web WorkersはJavaScriptにマルチスレッドの概念を導入し、メインスレッドとは別のスレッドで動作できる機能です。負荷の高い処理を別スレッドで実行することで、メインスレッドの動作を阻害することなく実行できます。
メインスレッドとWorkerスレッドはメッセージを送信することでデータのやりとりができます。このとき送信するデータはコピーされるため、巨大なデータのやりとりには時間がかかります。そこでJavaScriptではTransferableインターフェースという、コピーすることなく転送できるオブジェクトのインターフェースがあります。しかし、TransferableはArrayBufferなどごく一部のオブジェクトのみが対応しています。
Web Workersには制約があり、DOMの操作ができません。そのため、Web Workersを使用するのは、描画とは直接関係しない高負荷なデータ処理などの用途に限られていました。
これまでできなかったWorkerスレッドでの描画処理を実現できるOffscreenCanvas
OffscreenCanvasはその名の通り、オフスクリーンでレンダリングできる、DOMから切り離されたCanvasです。OffscreenCanvasはTransferableオブジェクトのため、Workerへ転送することによってWorkerスレッドでは従来実現できなかった描画処理を行えるというわけです。
OffscreenCanvasのデモを作成したのでご覧ください。
CanvasRenderingContext2DでOffscreenCanvasを使用する
Canvas要素にCanvasRenderingContext2Dでパーティクルを描画しています。まずはOffscreenCanvasを使用していないデモについて見てみましょう。パーティクル数を多くし意図的に高負荷にしているため、フレームレートが著しく落ちています。画面下のハートボタンを押すとCSSアニメーションが再生されますが、Canvas描画の高負荷の影響を受け、ボタンのアニメーションもフレームレートが落ちてカクカクしたものになっています。
次にOffscreenCanvasのデモを見てみましょう。CanvasのパーティクルアニメーションはOffscreenCanvasを使用していないデモの場合と同じくフレームレートが落ちていますが、画面左上に配置しているメインスレッドのフレームレート表示は60FPSを維持しています。また、ハートボタンのCSSアニメーションはなめらかに再生されます。このデモでは高負荷なCanvasのパーティクル描画をOffscreenCanvasでWorkerスレッドに処理させているため、メインスレッドは影響を受けず、軽快に動作します。
WebGLでOffscreenCanvasを使用する
OffscreenCanvasはWebGLでも使用できます。次のデモはThree.jsを使用してWebGL描画をOffscreenCanvasで行っています。
WebGL版のデモもCanvasRenderingContext2D版のデモと同じように、OffscreenCanvasを使用するとメインスレッドのフレームレートが高くなり、ハートボタンのCSSアニメーションもなめらかに再生されることがわかります。
OffscreenCanvasの使用方法
メインスレッドでDOMから取得したCanvas要素に対し、transferControlToOffscreen()
メソッドを呼ぶことでOffscreenCanvasオブジェクトを取得できます。このOffscreenCanvasオブジェクトのコンテキストに対して行った描画命令は自動的にもとのCanvas要素に反映されます。OffscreenCanvasオブジェクトをWorkerに転送し、描画を行います。
▼ メインスレッドの処理
const canvas = document.getElementById( 'myCanvas' ); |
const offscreenCanvas = canvas.transferControlToOffscreen(); |
const worker = new Worker( 'js/worker.js' ); |
worker.postMessage({canvas: offscreenCanvas}, [offscreenCanvas]); |
▼ Workerスレッドの処理
const offscreenCanvas = event.data.canvas; |
OffscreenCanvasはDOMと切り離されていること以外は通常のCanvasと同じため、Workerスレッドで受け取った後はOffscreenCanvasであることをほとんど意識することなく使用できます。たとえば、2D Canvasの場合は以下のようになります。
▼ Workerスレッドの処理
const offscreenCanvas = event.data.canvas; |
const context = offscreenCanvas.getContext( '2d' ); |
context.fillStyle = 'black' ; |
context.arc(50, 50, 100, 0, Math.PI * 2, false ); |
また、OffscreenCanvasはWebGLにも使用できます。
▼ Workerスレッドの処理
const offscreenCanvas = event.data.canvas; |
const context = offscreenCanvas.getContext( 'webgl' ); |
transferControlToOffscreen()
メソッドで取得したOffscreenCanvasはCanvasと同じように扱えるため、Three.jsなどのWebGLライブラリにも使用できます。
▼ Workerスレッドの処理
const offscreenCanvas = event.data.canvas; |
offscreenCanvas.style = {width: 0, height: 0}; |
const renderer = new THREE.WebGLRenderer({canvas: offscreenCanvas}); |
renderer.setSize(800, 600); |
const scene = new THREE.Scene(); |
const camera = new THREE.PerspectiveCamera(45, 800 / 600, 1, 10000); |
camera.position.set(0, 0, 1000); |
const geometry = new THREE.CubeGeometry(10, 10, 10, 1, 1, 1); |
const material = new THREE.MeshBasicMaterial({color: `black`}); |
const mesh = new THREE.Mesh(geometry, material); |
renderer.render(scene, camera); |
requestAnimationFrame(render); |
Workerスレッド内でrequestAnimationFrame()
メソッドを実行できるため、描画の更新も通常の方法とまったく同様に行えます。
※ Three.jsは内部でCanvas要素のstyle
にアクセスします。しかし、前述の通りOffscreenCanvasはDOM要素ではないため、style
を持ちません。Three.jsで使用する場合はランタイムエラーを避けるため、OffscreenCanvasオブジェクトに明示的にstyle
プロパティを付加します。
OffscreenCanvasの対象環境
2018年9月現在、OffscreenCanvasに正式に対応しているブラウザはまだChromeのみです。FirefoxとSafariではOffscreenCanvasは実験的な機能として提供されており、ブラウザのフラグを有効にすることで使用できます。正式に使用できる日もそう遠くない未来かもしれません。
※ 今回紹介したtransferControlToOffscreen()
メソッドはFirefox、Safariともに実験的な機能としてもまだ実装されておらず、フラグを有効にしても使用できません。
▲利用可能なブラウザについて。「Can I use…」より
おわりに
ユーザー操作のインタラクションの反応が悪いと、ユーザーエクスペリエンスを損ねます。OffscreenCanvasを使用することで負荷の高い描画処理をWorkerスレッドに移動し、スムーズなユーザー操作を実現できます。また、今回のデモのような用途以外にも、メインスレッドで使用する画像をOffscreenCanvasを使用してWorkerスレッドで作成するなど、さまざまな用途が考えられます。対応ブラウザがまだChromeのみですが、他のブラウザにも広く普及する日を楽しみに待ちましょう。