HTML-in-Canvas の実現性(a11y・性能・セットアップ・シェーダー)
layoutsubtree 下の子要素は実 DOM としてアクセシビリティツリーに残り、フォーカス・選択が動く。paint イベントは変化時だけ発火し、requestPaint() を呼ばなければ再描画は止まる。シェーダーで歪めた領域のヒットテストのズレと IME の動作が未解決。
調査時点: 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
developer.chrome.com/origintrials/#/view_trial/3478467762190286849でトークンを取得する。<meta http-equiv="origin-trial" content="<TOKEN>">を HTML に置くか、HTTP ヘッダーOrigin-Trial: <TOKEN>を返す。- 期間は 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 同期用シェーダー適用パターン
paintイベント内でrequestAnimationFrame(render)を呼ぶ。render()でgl.texElementImage2D()によりテクスチャを更新する。- フラグメントシェーダーでテクスチャをサンプリングしてエフェクトをかける。
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 |
| 色調グラデーション tint | WebGL2 + 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 時点)
- canvas 内
<input>での CJK IME 変換フロー。 - フォーカスリングの視覚表示タイミング。
- 発熱・バッテリー・60fps の定量データ。
- シェーダー歪み下のヒットテスト補正の公式手段。
- Firefox・Safari の実装見通し。
- retained(auto-updating)モードの仕様化時期。
- クラッシュを起こす CSS プロパティの具体的な一覧。
関連
更新履歴
- 2026-06-11: 初版(ローカル調査ノート)
- 2026-10-17: Wiki へ移植、概念ページとして再構成。冒頭に定義を追加
出典
- https://github.com/WICG/html-in-canvas/blob/main/README.md
- https://wicg.github.io/html-in-canvas/
- https://developer.chrome.com/blog/html-in-canvas-origin-trial
- https://github.com/WICG/html-in-canvas/issues/11
- https://html-in-canvas.dev/demos/elastic-bulge/
- https://frontendmasters.com/blog/the-web-is-fun-again-first-experiments-with-html-in-canvas/
- https://developer.playcanvas.com/user-manual/graphics/advanced-rendering/html-in-canvas/
同じトピック — Web
- 単一画面の Canvas ツールのスタック選定(vanilla TS / Svelte / Preact と PWA ツーリング)
- DAM カラオケ検索(clubdam)の挙動と制約
- Core Web Vitals と画像最適化
- favicon の最小構成(SVG + ICO + apple-touch-icon + manifest)
- フロントエンドのフレームワークとツールの動き(2025)
- HTML-in-Canvas API
- iOS Safari で canvas の画像を保存させる(download 属性 / Web Share / 長押し)
- 日本語 Web フォントのライセンスとサブセット化
- 個人サイトのライブウィジェットと外部 API の制限
- モダン CSS / Web API の対応状況(2025-2026)
- OPFS(Origin Private File System)
- Google Places API のキーの守り方(ブラウザ露出 vs サーバ中継)
- iOS の PWA 制限
- 機能別 React / TS ライブラリ対応表(2026-04 時点)
- URL 長の上限と日本語の URL エンコード膨張
- 書き出すテキストファイルの UTF-8 BOM と Markdown 継続行
- 縦書き Web の実装(writing-mode・ルビ・圏点・縦中横・禁則)
- PWA の Web Share Target(Android)の実測と落とし穴
- WebAssembly(Wasm)
- YouTube oEmbed で動画タイトルとチャンネル名を取る