新API「HTML-in-Canvas」を紹介
CanvasにHTMLを描くAPIは何を解決するのか?

はてなブックマークへ投稿
Xへポスト
URLをコピー
共有

波打つ入力フォームや発光するボタン、ウェブページ全体の派手なグリッチ表現やトランジション……。最近SNSでこうしたウェブページデモを見かける機会が増えてきました。こうしたデモの多くに利用されている新しい技術がHTML-in-Canvasと呼ばれるAPIです。まだ正式に利用できる機能ではありませんが、VFX-JSCanvasUIのように積極的にサポートするライブラリも登場しており、注目度の高い機能です。定番のThree.jsPixiJSなども実験的な対応を始めています。

▼ VFX-JS(ビジュアル表現用のライブラリ)のHTML-in-Canvasを使ったデモ

https://x.com/amagitakayosi/status/2059716302059847881

下の作例ではごく普通のウェブページや入力フォームにブラウン管風やリアルな雨粒といったさまざまな表現を追加しています。

https://x.com/vittorioretrivi/status/2041299652939542657

このポストでも述べられているとおり、ウェブデザインのアワードサイト『Awwwards』のようなビジュアル表現を重視する領域では必須のAPIになる可能性があります。

HTML-in-Canvasはひとことで言えば「Canvasの中にHTML要素を表示できる機能」です。上記の作例はどれもHTML-in-CanvasでCanvasに描画したHTML要素をWebGLなどでリアルタイムに加工することで実現しています。CSSでは到底不可能なインパクトのある作例が多数紹介されるようになり、一気に注目度が上がりました。

こうした演出は確かにインパクトがありますが、このAPIの本質はそこではありません。この記事では、もう少し地味な部分として、従来のCanvasの何が課題だったのか、このAPIは課題をどう解決するのか、そしてそれでも残る課題や問題はなんなのかを紹介します。

※ HTML-in-CanvasはW3CのWICGで議論されている提案段階の仕様です。2026年8月時点ではChromeでオリジントライアルが実施されています。本記事のデモ公開先はオリジントライアルに登録済みのため、対応するChromeではフラグを変更せずに試せます。ただし、まだ実用できる機能ではない点に注意してください。

Canvasの課題:貧弱すぎるテキスト描画

Canvasでリッチな画面、たとえばゲームのUIやインフォグラフィックスを作ろうとしたことがある方なら、たぶん最初にぶつかるのがテキストの描画でしょう。

Canvasが提供するテキスト描画用のfillText()メソッドは、指定した座標に1行の文字列を描くだけのシンプルなAPIです。

ctx.font = '40px "Noto Sans JP"';
ctx.fillText("吾輩は猫である", 0, 50, 400); // text, x, y, maxWidth(オプション)

fillText()メソッドで文字は表示できます。しかし、はみ出しても自動で折り返してはくれません。一部の単語だけ太字にすることもできませんし、末尾を「…」で省略したければ、文字幅をmeasureText()メソッドで測りながら自分で切り詰める必要があります。

fillText()で文字を書くだけだと右側(ACTUAL)のように潰れてしまい改行されない

Canvas標準のfillText()では改行すら簡単ではない

OGP画像の生成、チャートの軸ラベル、ゲーム内のUI。どれも「ちょっとテキストを載せたいだけ」なのに、気がつけばテキストレイアウトエンジンを自作している……。経験者であればこの辛さは共感してもらえるはずです。

参考:【Node.js】つらみを解消しながら動的なOGP画像を生成する:サーバーサイドでCanvasを使用してOGP画像を生成する解説。CSSと異なり確認や調整が難しく、苦労が多いこととがうかがえます。

さらに厄介なのが多言語対応です。アラビア語の連結処理やタイ語・ヒンディー語などの複雑な字形整形など、言語や文字体系によってさまざまな処理ルールが存在します。日本語も縦書きやルビ・禁則など、無意識に使っているルールがたくさんあります。ブラウザーのHTMLレンダラーはこうした言語処理を簡単・高度に利用できるように進化してきましたが、Canvasはこれまでほとんど追従できていませんでした。言語処理を各ウェブサイトの開発者が自前実装するのはほぼ不可能です。

Canvasの課題:UIパーツの自作地獄

苦労するのはテキストだけではありません。試しに、Canvas上に「きちんと押せるボタン」を作ってみてください。

矩形を描くだけなら数行で済みます。しかしそこにクリック判定を付け、ホバー時のカーソル変化を付け、Tabキーによるフォーカス移動に対応し、Enterキーでの実行を拾い、スクリーンリーダーに存在を通知する……。HTMLなら<button>タグと書くだけなのに、Canvasではぜんぶ自前で組み上げることになります

参考:Canvasで実際に押せる自作ボタンの例とコード(AI製)

もともとCanvasは「絵を描く」ための低レイヤーな仕組みです。そこにインタラクティブなUIを構築することが簡単なはずがありません。どうやって実現するか、HTMLとの分担や協力関係はどうあるべきか、もう20年にわたり、さまざまな文脈で議論が続いています。

HTML-in-Canvasの仕組み

前置きが長くなりました。ここからは実際のコードで動きを見ていきましょう。見栄えのよいデモを作るのは大変ですが、実はHTML-in-Canvas API自体の使い方は簡単で、基本は次の3つだけです:

  1. <canvas>要素にlayoutsubtree属性を指定する
  2. Canvasに描きたいHTML要素を<canvas>の直接の子要素として配置する
  3. drawElementImage()メソッドで描画する

今回は以下のHTMLを使ったシンプルな3つのデモを紹介します。演出目的ではないのでWebGPU・WebGLは使いません。

▼ サンプル用HTML

<canvas id="canvas" width="720" height="260" layoutsubtree>
  <article id="content">
    <p id="clock">12:34:56</p>
    <p>
      <ruby>吾輩<rp>(</rp><rt>わがはい</rt><rp>)</rp></ruby>は猫である。
      名前はまだ無い。どこで生れたかとんと<ruby>見当<rp>(</rp><rt>けんとう</rt><rp>)</rp></ruby>がつかぬ。……
    </p>
    <input type="text" placeholder="テキストを入力してください">
    <button type="button">クリック</button>
  </article>
</canvas>
<script>
  // 簡易的な時刻表示:1秒ごとにテキストを更新
  // この処理は外部ファイルに分けても構わない
  const clock = document.getElementById("clock");
  window.setInterval(() => {
    clock.textContent = new Date().toLocaleTimeString("ja-JP");
  }, 1000);
</script>

サンプル用HTMLの表示:時計が動き、ホバーに反応する様子

デモ①:ワンショットで転写する

もっともシンプルな使い方です。drawElementImage()メソッドを1回呼ぶだけです。

<button id="draw-btn">Canvasに描画</button>
<script>
  const canvas = document.getElementById("canvas");
  // 描画対象の要素を取得
  const content = document.getElementById("content");
  const drawButton = document.getElementById("draw-btn");
  const ctx = canvas.getContext("2d");

  drawButton.addEventListener("click", () => {
    ctx.reset();
    ctx.drawElementImage(content, 0, 0);
  });
</script>

動作の様子は以下のデモで確認できます。デモの初期状態ではまだカードのHTMLはCanvasの外に配置されています。[▼ Canvasに入れる]を押してから、[Canvasに描画]を押してください。Canvasの外にある状態ではエラーが発生して描画できないことも確認してみてください。

▼ デモ①の動作の様子

ルビも折り返しも、CSSで指定したフォントもレイアウトも、そのまま反映されています。あれほど苦労していたテキストレイアウトが、ブラウザーの標準機能で完全に画像化できました。OGP画像の生成やチャートのラベルのような用途なら、基本的な使い方だけで十分でしょう。もちろん、ここで得た画像をCanvas 2DやWebGPU・WebGLで加工すればグリッチやブラーといったエフェクトも当て放題です。

ただし、この例は用途によっては不完全です。drawElementImage()メソッドを呼んだ後も時計や入力欄の状態は変わりますが、Canvasに描いた内容は更新されません。描画されたものは、その瞬間のHTMLを写した「ただの絵」にすぎないためです。

デモ②:自動で再描画させる

このデモをリアルタイムに更新し、操作できるようにしてみましょう。なんとなく難しそうな気もしますが、実は<canvas>要素のrequestPaint()関数とpaintイベントを使うだけで簡単に対応できます。

// 要素の取得などは省略
// ctx.drawElementImage()をpaintイベントごとに実行する
canvas.addEventListener("paint", () => {
  ctx.reset();
  ctx.drawElementImage(content, 0, 0);
});

// 最初の描画を要求する
canvas.requestPaint();

これだけでCanvas内のHTMLが完全に動作するようになりました! 入力欄に文字を打てばCanvasにも即座に反映されますし、ボタンも押せます。たった数行の変更で、Canvasの中身が「生きたUI」になったわけです。

ところで、自動的に内容が再描画されるのはよいとして、ボタンがクリックできるのはなぜでしょうか? Canvasに書かれているもの自体はただの絵にすぎないため、ホバーやクリックのイベントはHTML要素が処理する必要があるはずです。

このトリックは簡単で、<canvas>の子要素として配置したボタンなどのHTML要素は見えていないだけでその場所に存在しているためです。<div>タグの中に<button>タグを置いたのと同様、その座標をクリックすれば子要素のボタンが先にイベントを受け取って通常の処理を行います。その結果表示が変わればpaintイベントが発火してCanvasも再描画されるのです。

※ この説明は概念的なもので、HTML-in-Canvasの正確な仕様とは異なります。ここでは大まかなイメージとしてとらえてください。

概念図:Canvas内のhtmlは見えていないが存在している

デモ③:描画位置とDOMを一致させる

最後の落とし穴が座標系の一致です。ここまでのデモでは素直に要素をdrawElementImage(content, 0, 0)で左上に描いていました。では描画位置を(100, 100)にしたらどうなるでしょうか? Canvasの座標系は回転や拡大縮小もできるので「見えないけど存在している」本物の要素と座標がずれてしまって正しくクリックできなくなってしまいます。

解決策はシンプルです。描画した座標に合わせてHTML要素の座標もずらしてしまいます。座標合わせを簡単にするために、drawElementImage()メソッドは戻り値として描画に適用された座標系の変換行列(DOMMatrixオブジェクト)を返します。この戻り値をHTML側のstyle.transformプロパティにそのまま設定すれば位置や変形が一致します。

canvas.addEventListener("paint", () => {
  ctx.reset();
  // カードの中心を基準に移動・拡大・回転する
  // 変換処理の詳細は省略

  // 描画結果の座標系(変換行列)が返るので、戻り値をHTML要素のスタイルに設定
  const transform = ctx.drawElementImage(content, 0, 0);
  content.style.transform = transform.toString();
});

以下のデモではカードを移動・拡大・回転できます。変形後も見えている位置にあるHTMLを操作できることを確認してみてください。

この3つのデモからもわかるように、HTML-in-Canvasが解決しようとしている課題は1つではありません。チャートのラベルやOGP画像の生成ならデモ①で十分です。決まった位置でのリアルタイムな表示更新や操作ならデモ②、Canvas上で移動・拡大・回転したフォームまで操作するならデモ③が必要になります。

アクセシビリティーはよくなる。ただし万能ではない

HTML-in-Canvasの利点としてよく挙げられるのがアクセシビリティーです。実際によくなる面は大きいのですが、過度な期待には注意が必要です。

よくなる面は明確です。fillText()メソッドで描いた文字は前述のとおりただの絵なので、アクセシビリティーツリーには含まれず、スクリーンリーダーも読めません。テキストの選択もコピーもできませんし、ブラウザーのページ内検索や翻訳も役に立たないでしょう。

HTML-in-Canvasで描いたものは裏に存在する本物のHTML要素なので、検索・コピー・翻訳・読み上げなど、すべてが自動的に動くようになります。この利点は支援技術を使っていない人にも嬉しい話で、「アクセシビリティー対応」というよりは、本来ウェブが当たり前にできるべきことにようやく対応できた、というほうが適切でしょう。

ただ、この仕組みを「HTML-in-CanvasでCanvasのアクセシビリティー問題が解決する」と受け取ると、ちょっと話が違ってきます。

まず、従来のCanvasでも子要素にフォールバックコンテンツを置くことはできました。<canvas>タグの中に<table>タグでデータを書いておけば、支援技術はデータを認識して読み上げます。現実には活用例が少ないのですが、活用されない理由は仕組みの問題というよりも、Canvasの描画内容とフォールバックコンテンツの二重管理が難しいからです。

HTML-in-Canvasはこの二重管理を自動である程度解決してくれますが、何もしなくてよいわけではありません。たとえばCanvasで円グラフを描くことを考えてみます。ラベルをHTML-in-Canvasで描けばそのテキストは読み上げられるようになりますが、円弧そのものは相変わらずctx.arc()メソッドで描いたピクセルです。支援技術に正しくグラフの意味を伝えるには、ラベルの構成や配置を意味的に正しくしたり描画しないテキストで説明を追加したりする必要があるでしょう。場合によっては従来どおり<table>タグのフォールバックコンテンツも必要かもしれません。<img>タグに適切なalt属性を付けるのと同様、必要な対応は設計や実装を行うエンジニアの仕事になります。

プライバシー:Canvas内で「描かれないもの」がある

HTML-in-Canvasにはプライバシーの懸念もあります。ピンとこない方もいるかもしれませんが、HTMLの描画結果はユーザー固有の情報を多数含んでおり、悪用されると個人の追跡や推定(フィンガープリンティング)に使われる恐れがあるのです。

ユーザー固有の情報には、OSのテーマカラーなどJavaScriptから直接読み取れない設定・訪問済みリンクの色などさまざまです。ここにIMEの変換候補や字幕・キャプションの表示設定などが入るとさらに情報が増えます。クロスオリジンの<iframe>タグや画像といった他ドメインのリソースなどもってのほかです。こうした機微な情報は通常JavaScriptからは読み取れないよう制御されていますが、まるごと画像化できてしまうと筒抜けになってしまいます。

この問題に対するChromeの仕様設計上の回答は明確で、機微な情報をリスト化して、それらは最初から描画しないとしました。現在の仕様では、たとえば次のようなものがCanvas上の描画から除外されています。

除外されるものの例:

  • IME関連:未確定文字列の下線や装飾
  • 入力補助の表示:スペルチェックや文法チェックの波線
  • ユーザー設定:OSのテーマカラーなどJavaScriptから直接読み取れない設定
  • 閲覧履歴:訪問済みリンクの色の変化
  • セキュリティ関連:未確定のオートフィル内容、クロスオリジンのコンテンツ
  • メディア設定:字幕・キャプションの表示設定

▼ デモ②で変換をCanvas内と外で行った場合の比較の図。HTML-in-Canvasでは変換時の文節区切りやスペルチェックの赤い点線が表示されていない

HTML-in-Canvasでは変換時の文節区切りやスペルチェックの赤い点線が表示されていない

ただし、このアプローチへの懸念もあります。リストで管理するということは、OSやブラウザーに新しいUIが追加されるたびにリストへの追加が必要になることを意味します。また、本来必要だから提供されているユーザーごとの表示を削ってしまうことは利便性やアクセシビリティーを損なう行為です。とくに単純にカッコいいエフェクトを当てるためだけにページ全体をHTML-in-Canvasで描画してよいかは慎重な判断が必要になるでしょう。

各社のスタンス:HTML-in-Canvasはリリースできるのか?

冒頭で紹介したとおり、HTML-in-Canvasはまだ仕様の合意に至っていません。2026年8月時点の状況を整理しましょう。

Chromeは積極的に実装を進めています。オリジントライアル(一般ユーザーに実験的機能を提供する仕組み)を実施して、実際のサイトからフィードバックを集めている段階です。当初はChrome 150までの予定でしたが、2026年6月にChrome 154(2026年10月リリース予定のChrome 155の手前)までの延長を決めています。延長の理由として「WebGL・WebGPU向けAPIとプライバシー面に大きな変更を入れたため」と説明されており、評価を継続して仕様合意を目指す方針と読み取れます。

Apple(WebKit)は公式なポジションを表明していません。ただし、2026年7月からWebKit本体への試験的な実装が始まっており、機能フラグやAPIの骨格が追加されるなど、対応に向けた動きは見られます。

Mozilla(Firefox)も立場は未表明です。2024年9月に検討用のIssueが立てられたまま「Needs proposed position」の状態が続いています。議論の場ではフィンガープリンティングやウェブ互換性の懸念を述べていますが、次の段階へ進めること自体には反対していません

まとめると、CanvasのテキストやUI周りの課題感は各社で共有されているものの、解決手段としてこのAPIをどこまで受け入れるかは未決着という状態と言えるでしょう。すぐに実務投入できる感触ではありませんが、ウェブの実務にあたる私たちもこのAPIをどう使うべきか考えはじめてよい時期かもしれません。

まとめ:カッコいいデモの先へ

冒頭で紹介したような目立つ演出はこのAPIのインパクトを伝えるには最高の素材です。でもこの記事で見てきたように、HTML-in-Canvasが本当に解こうとしている課題はもっと地味で、もっと切実なものです。テキストの折り返し、多言語の字形整形、フォーカス制御。Canvasで何かを作ったことがある人がもれなく経験してきた苦労を次世代に負わせないためにはこのAPIが必要です。

一方で、プライバシーやアクセシビリティーの面ではトレードオフは残ります。Canvas上のIMEの表示が削られるという話に「えっ」と思った方もいるかもしれません。安全性と利便性の合理的な線引きは難しい判断です。

この仕様がどういう形に着地するかは、実際にユーザーが何を求め、何に使われるかに左右される部分が大きいはずです。「派手なデモを見て気になっていた」という方は、ぜひ一度、ご自身のプロジェクトの中で「ここに使えそうだな」を探してみてください。公式の提案や議論に参加することは容易ではありませんが、SNSで作例や意見を公開するだけでも意味があるはずです。

参考リンク

Google検索でICS MEDIAの記事を見つけやすくしよう

優先するソースに追加すると、GoogleのトップニュースやAI検索でICS MEDIAの記事が表示されやすくなります。

Googleで優先するソースとして追加
SNSでシェアしよう
シェアいただくと、サイト運営の励みになります!
はてなブックマークへ投稿
Xへポスト
URLをコピー
共有
松本 ゆき

フロントエンドエンジニア。SIer&UXコンサルタントからフロントエンドエンジニアに転身。新しいアイデアを企画段階からプロトタイピングしていくことが得意です。趣味はお絵かきと開発。

この担当の記事一覧