こんにちは、福岡オフィスでエンジニアをしている petamoriken です。 ECMAScript と DOM API が好きで、気づいたらいつも仕様を眺めています。よろしくお願いします。
この記事では画像に関する新しい DOM API を紹介します。一般的に DOM API は二つのブラウザによる実装があれば安定していると言われていますが、プロダクトなどに採用する際には十分気をつけてください。また将来的に仕様が変更される可能性があることにも留意してください。
HTMLImageElement#decode()
明示的に画像のデコードをさせるメソッドです。Promise<void> を返します。
実は画像の load イベント完了後では画像の大きさを取得することは出来ますが、まだデコード処理はなされていません。今まで明示的にデコードさせたかったらこのようなコードを書いていたと思います。
この方法には問題があり、メインスレッドでデコード処理をしてしまうためブラウザを重くしてしまう可能性があります。
HTMLImageElement#decode() は仕様に in parellel と書いてあるようにメインスレッドを固めることなくデコードさせる事ができます。
OffscreenCanvas
HTMLCanvasElement と違い、DOM に依存していない Canvas です。 OffscreenCanvasRenderingContext2D がテキストの描画をすることが出来ない代わりに、Transferable で Dedicated Worker (非 Shared Worker) 内で描画処理をさせることが出来ます。もちろん WebGL も扱えます。
HTMLCanvasElement#transferControlToOffscreen() を呼ぶことで HTMLCanvasElement から OffscreenCanvas を作り、描画処理を委譲させることが出来ます。これによって生成された OffscreenCanvas を Worker 内で描画させることによって、メインスレッドを固めることなくアニメーションをさせることが出来ます。
詳しくは二年前に弊社のエンジニアである Ragg が書いた記事を御覧ください。
ただし当時は Window から requestAnimationFrame のタイミングを postMessage で送らないといけませんでしたが、最新仕様では DedicatedWorkerGlobalScope に requestAnimationFrame が生えているので単にそちらを使えばいいようです。
アニメーションをさせる必要がなく、一枚の画像を描画したい場合は今までと同じように OffscreenCanvas#convertToBlob(options) で Blob を作り、URL.createObjectURL(blob) で Blob URL を作って Worker から Window に送ることも出来ますが、OffscreenCanvas#transferToImageBitmap() を呼ぶと後述する ImageBitmap をゼロコピーで作ることが出来ます。
ImageBitmap
一枚の画像を扱うことが出来るインターフェースです。なんと Transferable かつ Serializable です。すなわち Window と Worker で送り合うことも出来れば、IndexedDB に格納することも出来ます。
ImageBitmap は HTMLImageElement#decode() を呼んだときのように、事前に描画のための処理を済ませています。そして CanvasImageSource の一つであり、CanvasDrawImage#drawImage を使って描画することが出来ます。ちなみに CanvasImageSource について完全に余談ですが、最近 CSS Typed OM の中で CSSImageValue が CanvasImageSource の一つであると拡張されていたりします。
createImageBitmap(image [, options])
createImageBitmap(image, sx, sy, sw, sh [, options])
ImageBitmap は前述した通り OffscreenCanvas からも作ることが出来ますが、Window と Worker 内で使える createImageBitmap を使っても生成することが出来ます。第一引数には先程の CanvasImageSource の他に、Blob と ImageData を入れることが出来ます。後述する ImageBitmapRenderingContext を使うとわざわざ Blob を URL.createObjectURL(blob) に入れずに HTMLCanvasElement を使って描画することが出来て便利です。また options 内で生成する ImageBitmap の大きさを変更させることが出来ます。
残念ながら Firefox でバグがあり sx, sy, sw, sh を省略した場合に options を受け入れなかったり、リサイズ系の options を無視してしまったりすると報告されています。
ImageBitmapRenderingContext
HTMLCanvasElement#getContext("bitmaprenderer", options) で作ることが出来る RenderingContext です。ただ一つ transferFromImageBitmap(imageBitmap) というメソッドのみを持ちます。これは ImageBitmap から画像をゼロコピーで受け取ります。譲渡された ImageBitmap は空になります。
このときの HTMLCanvasElement の見た目ですが、Chrome で試したところ HTMLImageElement と同じような感じになってそうです。ただし HTMLCanvasElement の width, height にはデフォルト値 300, 150 が入っていますので、ImageBitmap と同じサイズの画像を表示させてい場合は譲渡する前にそのサイズを HTMLCanvasElement に渡す必要がありそうです。ちなみに描画後に width, height を変更させても HTMLCanvasElement の描画がリセットされることはなさそうです。
残念ながら Chrome でバグがあり、ImageBitmapRenderingContext を持つ HTMLCanvasElement で toDataURL/toBlob を呼ぶと同じサイズの透明な画像を生成してしまうことが報告されています。特に役に立たないと思われますがこれを判定するコードが以下になります。
いかがだったでしょうか。このあたりの実装ですが主に Chrome と Firefox のみとなっていて更に実装されていてもまだバグがあるような状態ですので、もうしばらくはプロダクト採用はできなそうだと思っています。
最近 Edge が Chromium ベース実装になると発表があり、見かけ上の実装されるブラウザは多くなりそうではありますが多様性の観点からとても残念に思いますね。Edge が他のブラウザを差し置いて Async Functions を真っ先に実装して、とても勢いがあった時期を覚えているだけに悲しい気持ちになります。
ピクシブ株式会社では Web の行く末をより良いものとするために積極的に仕様策定の議論に参加するエンジニアを募集しています!
福岡: 中途採用 / インターンシップ(学生)
東京: インターンシップ(学生)
