第1回

無料公開中公開翌日4時まで

HTML in Canvasの基本とCanvas 2Dでの使い方

HTML in CanvasはCanvas 2DやWebGL、WebGPUに、HTML/CSSを直接描画するためのAPIで、Chromeで試験提供中です。機能やユースケースを紹介します。

(685号)

更新

目次
シェア

はじめに

昨年(2025年)の記事「HTMLを描画するHTML-in-Canvas API」では、Chromeのフラグを有効にして試す段階の「HTML in Canvas」を取り上げました。あれから約一年。HTML in CanvasはChromeのOrigin Trialを経て、2026年11月3日にリリースされたChrome 157で正式に利用できるようになりました。

そこで本シリーズでは、あらためて仕様の現状を整理しつつ、Three.jsと組み合わせた応用例まで踏み込んでいきます。Canvas 2DとWebGLで動くデモを用意しましたので、手元のGoogle ChromeやEdgeで動かしながら読み進めてみてください。

HTML in Canvasとは

まず、HTML in Canvasについておさらいしておきます。

HTML in Canvasは、canvas要素にHTML/CSSを直接描画できるようにするWICGの提案です。Canvas 2Dだけでなく、WebGL、WebGPUの描画コンテキストにも対応する、DOMとCanvasの橋渡しをするAPIです。

これまでCanvasは、テキストのスタイリング、レイアウト、アクセシビリティ、国際化などの面で機能が少なく、長年課題を抱えていました。HTML in Canvasは、HTMLの機能やCSSのレイアウト能力をCanvas側に持ち込むことで、それらをまとめて解決できるAPIです。

フラグからOrigin Trialへ

HTML in Canvasの提案は、2024年8月ころから議論が始まりました。その後、2025年6月リリースのGoogle Chrome 138以降でDev Trialとして、コマンドラインでのフラグでCanvasDrawElementを有効にすればHTML in Canvasが動作する実験機能として実装されました。

さらに、2026年5月、Chrome 148以降でHTML in CanvasのOrigin Trialが開始されました。Origin Trialに登録したオリジン(ドメイン・サブドメイン配下)であれば、訪問者はGoogle ChromeやEdgeのフラグ操作なしにHTML in Canvasを利用できます。ですから、それらのブラウザを使っている一般ユーザーには、HTML in Canvasを使ったコンテンツを届けられるようになったわけです。

2026年のGoogle I/OのセッションでもHTML in Canvasが取り上げられました。HTML in Canvasの基本的な使い方から、WebGLと組み合わせた応用例まで、幅広く紹介されています。セッションの内容はYouTubeでも公開されています。Chromeチームによるデモと解説があるので、視聴してみましょう。

Origin Trial(オリジントライアル)とは

特定のオリジンに対してのみ、まだ標準化前の機能を有効化できるGoogle Chromeの仕組みです。登録ページでドメインを登録し、そのドメインに対するトークンを取得して<meta>タグでページに埋め込むか、HTTPヘッダーで送信することで、訪問者のブラウザでも一定期間実験的なAPIが動くようになります。

Origin Trialトークンの埋め込み
<meta http-equiv="origin-trial" content="YOUR_TOKEN_HERE">

Origin Trialのページは日本語では一部リンク切れがあるため、登録手続きを行う際は英語ページの利用がおすすめです。

スクリーンショット:Chrome Origin Trialsのhtml-in-canvas Trial登録ページ。対応バージョン(Chrome 148〜154)、終了日、Web Origin(ドメイン入力欄)、想定日間ページビュー数の各フィールドが表示されている

当面はChrome系の先行実装と捉える

注意しなければいけないのは、Safari、Firefoxなどすべてのベンダー間の合意には至っていないことです(コラム参照)。プロダクション利用はまだ時期尚早で、現時点では「Google ChromeやEdgeを使うユーザー向けのプログレッシブエンハンスメント」や、社内ツール・デモ・プロトタイプといった環境を限定できる用途が現実的なところです。

とはいえ、Origin Trialによって新しいAPIをより気軽に試せるようになった意義は小さくありません。一般ユーザー向けに広く展開する段階ではないものの、新しいAPIで何ができるか、などの実践的な検証を行い、そのフィードバックを仕様に反映していく重要な期間といえます。実装が世に出ることで仕様議論も具体化していくはずなので、SafariとFirefoxの動向は、今後も注目していく価値があります。

HTML in Canvas、ほかのブラウザはどう動いているか

SafariとFirefoxはHTML in Canvasにどう向き合っているのでしょうか。結論からいうと、現時点で実装に踏み出しているのはChromium系のみで、ほかのベンダーはまだ議論の段階です。

  • Safari(WebKit)

    Googleが2026年3月にWebKitへスタンス表明を求めるissueを立てましたが(standards-positions issue #630)、WebKitチームはコメントもpositionラベルも付けておらず、現時点では未返答のままです。

  • Firefox(Mozilla)

    一方、Mozillaのissue #1076では、onpaintによる同期レンダリングへの懸念、「DOMの描画結果をJSからピクセルとして読み取れる」ことによるフィンガープリント・情報漏洩などといった理由から、用途によってはヒットテスト不要なよりシンプルなキャプチャAPIで十分ではないか、という立場をとっています。

基本的な仕組み

さて、ここからは具体的な実装を見てみましょう。

HTML in Canvasの使い方は、おおまかには次の3ステップに集約されます。

  1. <canvas content="drawable">を用意し、そのcanvasの中に、drawable属性を付けたHTMLを書く
  2. onpaintイベントで再描画タイミングを受け取る
  3. 描画コンテキストに応じたAPIで、drawable属性を付けた要素のDOMをcanvasに描画する

それぞれのポイントを順に見ていきます。

content=“drawable”属性とdrawable属性

通常、canvas要素の中にDOMを置いても、ブラウザはそれをレンダリングしません。また、レイアウトの計算もされません。canvas要素にcontent="drawable"属性を付けると、ブラウザに「中の要素もレイアウト計算してください」と伝えられます。

さらに、Canvasに描画したい要素には、drawable属性を付けて明示します。描画したい要素は、canvasの子孫であればどこに置いてもかまいません。drawable属性を付けた要素は、その子孫要素ごと描画の対象になります。

content属性とdrawable属性を付ける
<canvas content="drawable">
  <div id="content" drawable>
    <p>Hello <strong>Canvas!</strong></p>
  </div>
</canvas>

この時点では、content="drawable"属性によって、canvasの中の要素はレイアウト計算の対象になるものの、画面には直接表示されません。Canvasに明示的に描画されることで初めて目に見える形で現れます。

onpaintで再描画を受け取る

<canvas>にonpaintイベントが追加されました。content="drawable"を付けた<canvas>の中の要素はブラウザが直接描画しないため、自前で描画するタイミングを知る手段が必要です。onpaintはブラウザが本来DOMを再描画するタイミングをイベントとして通知し、「いまCanvasを更新すれば最新状態のDOMを反映できる」という合図になります。Canvas内のDOMが変化したり、CSS Transition、CSS Animation、SVG Animationでの変化があると、自動で発火します。

onpaintの基本パターン
canvas.onpaint = ( event ) => {

  // ここで再描画
  // event.changedElements で変化した要素だけを拾うこともできる

};

DOMやCSSの状態に変化がないときでも、canvas.requestPaint()を呼ぶことでonpaintを明示的に発火させることができます。

コンテキストに応じた描画API

Canvasの描画コンテキストごとに、専用のAPIが用意されています。

コンテキスト メソッド
Canvas 2D drawElementImage()
OffscreenCanvas drawElementImage()
WebGL texElementSubImage2D()
WebGPU drawElementImageToTexture()

どのAPIも入力は<canvas content="drawable">の中の、drawable属性を付けた要素のDOMです。Canvas 2DとOffscreenCanvasは「描画」、WebGLとWebGPUは「テクスチャーへのコピー」と、用途で分かれている点がポイントです。

Canvas 2Dで使ってみる

まずは一番シンプルなCanvas 2Dの例から見ていきましょう。Canvas 2Dでは、CanvasRenderingContext2DのdrawElementImage()メソッドを使います。

Canvas 2Dの最小構成
<canvas id="canvas" content="drawable">
  <div id="content" drawable>
    <p>Hello <strong>Canvas!</strong></p>
  </div>
</canvas>
<script>
  const canvas  = document.getElementById( 'canvas' );
  const content = document.getElementById( 'content' );
  const ctx     = canvas.getContext( '2d' );

  canvas.onpaint = () => {

    ctx.reset();
    ctx.drawElementImage( content, 0, 0 );

  };
</script>

drawElementImage()の第二・第三引数はCanvas上の描画先座標(x, y)です。描画のたびにctx.reset()で前フレームの状態をクリアし、再描画する、というのが一連の流れです。

drawElementImage()で描画すると、描画先は単一のcanvas要素ではあるものの、ブラウザはDOMのヒットテスト位置を、Canvas上の描画位置に自動で合わせます。ヒットテスト位置とは、クリックやホバーを判定する位置のことです。フォーカスリングや、スクリーンリーダーなどの支援技術が参照する要素の位置も、同様に合わせられます。そのため、描画した要素の位置を見た目に合わせる処理を、開発者が書く必要はありません。

なお、同じ要素を複数回描画する場合など、位置合わせを自分で制御したいときは、drawElementImage()に{ preserveElementGeometry: true }を渡して自動の位置合わせを止め、canvas.updateElementGeometry()で位置を指定できます。

テキスト入力の自動同期

実用的なユースケースを見ていきます。次のデモは、トレーディングカードゲーム風のカードエディタです。HTMLでマークアップしたカードを、Canvas 2Dに描画しています。デモは別タブや別ウインドウでご確認ください。

テキスト入力に追従するカードエディタ

カード全体(.Card)にdrawable属性を付け、drawElementImage()で描画しています。前述のとおり、描画した位置はクリックなどの対象として自動で登録されるので、Canvasに描かれた入力欄をそのままクリックして、文字を入力できます。

カード名やルールテキスト部分はinputやtextareaになっており、入力欄に文字を打ち込むと、ほぼ遅延なくCanvasの描画にも反映されます。テキスト編集による再描画タイミングは、onpaintが勝手に検知してくれるので、開発者側で「いつ再描画するか」を考える必要はありません。

WebFontsや<img>の読み込み完了も同様です。旧来のCanvasでは「フォントが読み込まれるまで待ってから描画」のようなロード待ちの制御が必要でしたが、HTML in Canvasでは、フォントロードや画像のロード完了による描画更新タイミングもonpaintとして自動で検知されます。つまり、フォントや画像のロード完了を意識せずに済むわけです。

完成したカードは、canvas.toDataURL()で画像としても保存できます。これはつまり、HTMLのキャプチャーを画像としてエクスポートできるということです。HTMLは長年、そのまま画像にすることはできませんでした。HTML in Canvasは、HTMLを画像として出力する手段を提供する、初めてのAPIでもあります。

Canvasのサイズ設定

canvas要素を子要素と同じ大きさにしたい場合には、子要素のサイズをcanvas.widthとcanvas.heightに指定します。

レティナディスプレイなどの高解像度ディスプレイを活かしたい場合は、

  • canvas.widthとcanvas.height
  • canvas.style.widthとcanvas.style.height

の両方を指定します。

canvas.style.widthとcanvas.style.heightには、子要素と同じCSSサイズを指定し、canvas.widthとcanvas.heightには物理ピクセル数(上記の解像度倍のサイズ)を指定します。

これにより、ピクセル密度が、

  • 1の環境では、<canvas width="512" style="width: 512px;">
  • 2の環境では、<canvas width="1024" style="width: 512px;">

とするわけです。

次のようにResizeObserverを使って、子要素のサイズに合わせてcanvasをリサイズするのが便利です。これなら、複数の画面でブラウザのウインドウを行き来した場合にも、常に最適な解像度で描画できます。

物理ピクセルでcanvasをリサイズ
canvas.style.width  = `${ content.offsetWidth }px`;
canvas.style.height = `${ content.offsetHeight }px`;

const observer = new ResizeObserver( ( [ entry ] ) => {

  const px = entry.devicePixelContentBoxSize[ 0 ];
  canvas.width  = px.inlineSize;
  canvas.height = px.blockSize;

} );
observer.observe( canvas, { box: 'device-pixel-content-box' } );

なお、従来は次のように指定することが多かったですが、ResizeObserverを使わない場合、解像度に変化があった際に適切な解像度を自動指定したい場合には不向きです。

従来のサイズ指定
const dpr = window.devicePixelRatio || 1;
canvas.style.width  = `${ content.offsetWidth }px`;
canvas.style.height = `${ content.offsetHeight }px`;
canvas.width  = content.offsetWidth * dpr;
canvas.height = content.offsetHeight * dpr;

ここまでのまとめ

前回(2025年)の記事からちょうど一年、HTML in CanvasはOrigin Trialに移行し、フラグ操作なしでコンテンツを届けられる段階になりました。今回は、APIの基本的な仕組みと、Canvas 2Dでの使い方を見てきました。

canvas要素にcontent="drawable"属性を付けて中の要素をレイアウト対象にし、描画したい要素にはdrawable属性を付けます。そして、onpaintで再描画タイミングを受け取って描画します。この組み合わせがHTML in Canvasの基本パターンです。

次回は、WebGLでシェーダーを適用する方法と、DOMと描画を連動させる仕組みの詳細、アクセシビリティの確保について見ていきます。お楽しみに。

この記事の内容についての意見・感想を送る
CodeGridのDiscordで知識を深めよう。参加する。

人気のシリーズ