HTML-in-Canvas の実現性(a11y・性能・セットアップ・シェーダー)

一言まとめ

layoutsubtree 下の子要素は実 DOM としてアクセシビリティツリーに残り、フォーカス・選択が動く。paint イベントは変化時だけ発火し、requestPaint() を呼ばなければ再描画は止まる。シェーダーで歪めた領域のヒットテストのズレと IME の動作が未解決。

作成 · 更新 · #canvas#webgl#accessibility#performance
目次 7 sections
  1. 01 a11y・フォーカス・入力
  2. 02 性能・再描画モデル
  3. 03 セットアップ
  4. Origin Trial
  5. ローカルのフラグ
  6. 最小コード(2D)
  7. WebGL(texElementImage2D)
  8. WebGPU・OffscreenCanvas・DOM 同期
  9. 04 シェーダー適用パターン
  10. 05 未解決(2026-06 時点)
  11. 06 関連
  12. 07 更新履歴

調査時点: 2026-06。概要・API・標準化状況は HTML-in-Canvas API にある。

HTML-in-Canvas は、実際の HTML 要素を DOM として保持したまま、その見た目を canvas に描画するための API 提案。このページは、その方式の入力・アクセシビリティ・性能の実現性を扱う。概要は HTML-in-Canvas API。

a11y・フォーカス・入力#

  • layoutsubtree 付き canvas の子要素は通常の DOM ノードと同じくアクセシビリティツリーに公開される。描画元が実 DOM なのでツリーは自動で同期する(仕様の言い方では “Drawn elements are the fallback content”)。find-in-page・テキスト選択・スペルチェック・辞書参照・ブラウザ拡張からのアクセスも動く。
  • Issue #11: 描画前の子要素も支援技術 API に公開されてしまう。暫定回避は hidden / inert / aria-hidden で手動制御。
  • フォーカス挙動は維持され、レイアウトに参加するので getBoundingClientRect() も有効な値を返す。フォーカスリングが canvas 描画面に正しく出るかは一次情報に明記がない。
  • ヒットテストは DOM 上の位置を基準に行う。drawElementImage() が返す transform を element.style.transform に入れて追従させる。
canvas.addEventListener("paint", () => {
ctx.reset();
ctx.translate(250, 150);
ctx.rotate(angle);
ctx.translate(-250, -150);
const transform = ctx.drawElementImage(content, 0, 0);
content.style.transform = transform.toString(); // DOM 位置と描画位置を同期
});
  • シェーダーで歪めた場合: 仕様は「子要素の CSS transform は描画では無視され、ヒットテストと a11y にだけ効く」と明記している。フラグメントシェーダーで視覚的に歪めた結果はヒットテストに反映されない。Elastic Bulge デモも歪み領域下の要素へのヒットテストは実装していない。「歪みあり + ヒットテスト正確」は現状サポート外で、逆行列による補正は仕様外の独自実装になる(Issue #140)。
  • IME: “Real cursors, real selection, real autocomplete” をうたうデモはあるが、CJK の IME 変換フローが canvas 内の <input> で動くことを実証した一次情報は確認できなかった。

性能・再描画モデル#

  • paint イベントは子要素の描画が変化したときだけ、1 フレームに 1 回発火する。paint 内の canvas 描画は同フレームに反映され、DOM 変更は次フレームに回る。
  • requestPaint() を呼ばなければ paint は発火せず、静止コンテンツでは GPU 描画ループが止まる。毎フレーム再描画にはならない。
  • 「将来の auto-updating(retained)モード」は未実装。スクロールや CSS アニメーションをメインスレッドなしで追従させる構想はあるがスケジュール未定。
  • getImageData() は canvas コマンドバッファの同期フラッシュを要する可能性がある。WebGL の texElementImage2D は CPU ラウンドトリップを避けて GPU テクスチャに直接書くので、2D context 経由の getImageData より効率がよい。
  • 特定の CSS プロパティでクラッシュする報告があるが、具体的なプロパティ一覧は公開されていない。60fps 維持時・500 要素超の DOM・モバイルでの発熱やバッテリーの定量データは公開情報にない。

セットアップ#

Origin Trial#

  1. developer.chrome.com/origintrials/#/view_trial/3478467762190286849 でトークンを取得する。
  2. <meta http-equiv="origin-trial" content="<TOKEN>"> を HTML に置くか、HTTP ヘッダー Origin-Trial: <TOKEN> を返す。
  3. 期間は Chrome M148〜M154(2026-05-19 開始、2026-06-10 に延長)。デスクトップ・Android・WebView が対象。

ローカルのフラグ#

chrome://flags/#canvas-draw-element を Enabled にする(Finch feature 名 CanvasDrawElement)。Chrome Canary 149 以降、Brave Stable(Chromium 147+)でも同じフラグが使える。

最小コード(2D)#

<canvas id="canvas" layoutsubtree>
<div class="content">
<h1>Hello</h1>
<input type="text" placeholder="名前">
<button>送信</button>
</div>
</canvas>
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const content = canvas.querySelector(".content");
canvas.requestPaint(); // 初回の発火を要求
canvas.addEventListener("paint", () => {
ctx.reset();
const transform = ctx.drawElementImage(content, 0, 0);
content.style.transform = transform.toString();
});

drawElementImage() のオーバーロードは (el, dx, dy)、(el, dx, dy, dWidth, dHeight)、(el, sx, sy, sWidth, sHeight, dx, dy)、(el, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) の 4 つ。

WebGL(texElementImage2D)#

const gl = canvas.getContext("webgl2", { alpha: true, premultipliedAlpha: true });
// paint の中から requestAnimationFrame 経由で呼ぶ。直接呼ぶと
// 'no cached paint record for element' になる
canvas.addEventListener("paint", () => requestAnimationFrame(render));
function render() {
gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA, content); // Chrome 150+ の 3 引数形式
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

Chrome 148〜149 は旧 5 引数形式 texElementImage2D(target, level, internalformat, format, type, element)。移行期間は try { 新 } catch { 旧 } で吸収する。

WebGPU・OffscreenCanvas・DOM 同期#

device.queue.copyElementImageToTexture({ source: el }, { destination: { texture } });
const img = canvas.captureElementImage(el); // Transferable。Worker に postMessage で送れる
const m = canvas.getElementTransform(el, drawTransform); // WebGL/WebGPU での DOM 同期用

シェーダー適用パターン#

  1. paint イベント内で requestAnimationFrame(render) を呼ぶ。
  2. render() で gl.texElementImage2D() によりテクスチャを更新する。
  3. フラグメントシェーダーでテクスチャをサンプリングしてエフェクトをかける。
  4. gl.drawArrays() で描く。
precision mediump float;
varying vec2 v;
uniform sampler2D u;
void main() {
vec4 c = texture2D(u, v);
vec3 tint = mix(vec3(1.5, 0.5, 0.5), vec3(0.5, 0.5, 1.5), v.x);
gl_FragColor = vec4(c.rgb * tint * c.a, c.a);
}
実証済みのエフェクト実装
Elastic Bulge(ラジアル変位)・ポインタ追従歪みWebGL2 + displacement shader
色調グラデーション tintWebGL2 + fragment shader
Liquid Glass / 屈折WebGL + pointer passthrough
Frosted glass公式デモ
CRT・雨・ピクセレーション・流体ディストーションThree.js + custom shader material / React Postprocessing / react-fluid-distortion
3D room 内のライブ Web コンテンツThree.js HTMLTexture

Three.js r184 の InteractionManager が毎フレーム CSS matrix3d を計算して DOM をメッシュに追従させ、PlayCanvas は getElementTransform() ベースの HtmlSync を提供する。いずれも「視覚的に歪めた領域ではヒットテストがずれる」点は同じで、エフェクトによってはアクセシビリティを損なう。

未解決(2026-06 時点)#

  1. canvas 内 <input> での CJK IME 変換フロー。
  2. フォーカスリングの視覚表示タイミング。
  3. 発熱・バッテリー・60fps の定量データ。
  4. シェーダー歪み下のヒットテスト補正の公式手段。
  5. Firefox・Safari の実装見通し。
  6. retained(auto-updating)モードの仕様化時期。
  7. クラッシュを起こす CSS プロパティの具体的な一覧。

関連#

更新履歴#

  • 2026-06-11: 初版(ローカル調査ノート)
  • 2026-10-17: Wiki へ移植、概念ページとして再構成。冒頭に定義を追加

出典

  1. https://github.com/WICG/html-in-canvas/blob/main/README.md
  2. https://wicg.github.io/html-in-canvas/
  3. https://developer.chrome.com/blog/html-in-canvas-origin-trial
  4. https://github.com/WICG/html-in-canvas/issues/11
  5. https://html-in-canvas.dev/demos/elastic-bulge/
  6. https://frontendmasters.com/blog/the-web-is-fun-again-first-experiments-with-html-in-canvas/
  7. https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/html-in-canvas/

同じトピック — Web