HN 日本語サマリー

← 一覧へ戻る
Web開発

HTML-in-Canvas API のオリジン試用版

The HTML-in-Canvas API origin trial (developer.chrome.com)

108 pointsby andrewstuart48 コメント

要約

Chromeは、HTMLコンテンツを直接Canvasに描画し、インタラクティブ性、アクセシビリティ、ブラウザ機能を維持できる新しいHTML-in-Canvas APIのオリジン試用版を発表しました。このAPIにより、DOMの豊富な機能とCanvasの低レベルグラフィックスパフォーマンスを組み合わせた、これまで不可能だったウェブ体験の構築が可能になります。

全文翻訳

HTML-in-Canvas API のオリジン試用版を発表 コレクションで整理整頓を維持する 好みに基づいてコンテンツを保存および分類します。

[画像: Thomas Nattestad]

Thomas Nattestad

[画像: Natalia Markoborodova]

長年、ウェブ開発者はウェブ上で複雑で高度にインタラクティブなビジュアルアプリケーションを構築する際に、厳しいアーキテクチャ上の選択を迫られてきました。DOMの豊富なセマンティック機能に頼るか、それとも低レベルグラフィックスパフォーマンスのために<canvas>要素に直接レンダリングするか、です。

新しい実験的なHTML-in-Canvas API—現在オリジン試用版で利用可能—を使えば、選択する必要はありません。このAPIにより、UIをインタラクティブで、アクセシブルで、お気に入りのブラウザ機能に接続したまま、DOMコンテンツを直接2DキャンバスまたはWebGL/WebGPUテクスチャに描画できます。HTMLと低レベルグラフィックス処理を組み合わせることで、これまで不可能だった体験を創造できます。

DOM 対 Canvas

この新しいAPIの力を理解するには、DOMとCanvasそれぞれの強みを比較することが役立ちます。

DOMはウェブUIの基盤です。セマンティックに理解されたコンテンツを使用してリッチなインターフェイスを作成し、テキストレイアウトソリューションを標準で提供します。これにより、ユーザーはウェブページ全体で一般的な操作をシームレスに実行できます。テキストを選択してコピーしたり、画像を右クリックして保存したりするなど、私たちが当然と思っているようなことです。DOMは、アクセシビリティツール、翻訳、ページ内検索、リーダーモード、拡張機能、ダークモード、ブラウザズーム、自動入力といった不可欠なブラウザ機能とも統合されています。

一方、Canvas(およびWebGL/WebGPU)は、ピクセルグリッドを制御するための低レベルアクセスを可能にし、高度な2Dおよび3Dグラフィックスを実現します。ゲームや複雑なウェブアプリケーション(Google DocsやFigmaなど)には、このパフォーマンスの高い低レベルアクセスが必要です。Canvasは基本的にピクセルグリッドであるため、レスポンシブテキストのような機能をサポートするには複雑なカスタムUIロジックが必要となり、バンドルサイズが大幅に増加していました。決定的に、DOMに統合されている強力なブラウザ機能はすべて、UIが静的なキャンバスのピクセルグリッド内に閉じ込められると完全に機能しなくなります。

CanvasへのDOM導入の利点

HTML-in-Canvas APIは、両方の長所を活かすための架け橋となります。<canvas>要素内にHTMLを配置し、その変換を同期することで、コンテンツが完全にインタラクティブであり続け、すべてのブラウザ統合が自動的に機能することを保証します。

<canvas>要素内でDOMがUIを処理できるようにすることで、以下の利点が得られます。

  • テキストレイアウトとフォーマット: CSSスタイルが適用された複数行または双方向テキストを含む、簡略化されたテキストレイアウトとフォーマット。
  • フォームコントロール: 豊富なカスタマイズオプションを備えた、表現力豊かで使いやすいフォームコントロール。
  • テキスト選択、コピー/ペースト、右クリック: ユーザーは3Dシーン内のテキストをハイライトしたり、ネイティブに右クリックコンテキストメニューを表示したりできます。
  • アクセシビリティ: キャンバス内にレンダリングされたコンテンツは、アクセシビリティツリーに公開されます。アクセシビリティシステムは、通常のHTMLと同様にUIを解析し、スクリーンリーダーなどのシステムに公開できます。
  • ページ内検索: ユーザーはページ内検索(Ctrl/Cmd+F)を使用してテキストを検索でき、ブラウザはそれをWebGLテクスチャ内で直接ハイライトします。
  • インデックス作成可能でAIエージェントが操作可能: ウェブクローラーやAIエージェントは、2Dおよび3Dシーンにレンダリングされたテキストをシームレスにインデックス作成および読み取ることができます。
  • 拡張機能統合: ブラウザ拡張機能がネイティブに動作します。たとえば、テキスト置換拡張機能は、3Dメッシュにレンダリングされたテキストを自動的に更新します。
  • DevTools統合: Chrome DevToolsで、WebGL/WebGPU UI要素を含むキャンバスコンテンツを直接検査できます。インスペクターでCSSスタイルを調整すると、3Dテクスチャ上で即座に更新されます。

高レベルなユースケース

このAPIは、いくつかのドメインで信じられないほどの可能性を解き放ちます。

  • 大規模なキャンバスベースのアプリケーション: 重量級のウェブアプリは、キャンバス駆動のワークスペースに複雑なアプリケーションUIコンポーネントをネイティブにレンダリングできるようになり、アクセシビリティが向上し、バンドル重量が削減されます。
  • 3Dシーンとゲーム: マーケティングサイト、没入型WebXR体験、ウェブゲームは、実際のDOMテキストを使用する3D書籍や、コピー&ペーストをネイティブにサポートするゲーム内ターミナルのように、完全にインタラクティブなウェブUIを3Dシーンに配置できるようになります。

APIの使用方法

APIの使用は3つのフェーズで行われます。キャンバスのセットアップ、キャンバスへのレンダリング、そして要素が画面上のどこに物理的に配置されているかをブラウザに知らせるためのCSSトランスフォームの更新です。

前提条件

HTML-in-Canvas APIは、Chrome 148から150でオリジン試用版として提供されています。ご自身のサイトでテストするには、Chrome Canary 149以降でchrome://flags/#canvas-draw-elementフラグを有効にして使用してください。他のユーザーのためにAPIを有効にするには、オリジン試用版に登録してください。

ステップ1: 基本的なCanvasセットアップ

まず、<canvas>タグにlayoutsubtree属性を追加します。これにより、ブラウザはキャンバス内にネストされたコンテンツを認識し、キャンバス内に表示されるように準備され、アクセシビリティツリーに公開されます。

<canvas id="canvas" style="width: 200px; height: 200px;" layoutsubtree>
  <div id="form_element">
    <label for="name">Name:</label> <input id="name" type="text">
  </div>
</canvas>
キャンバスグリッドのサイズ設定

レンダリングされたコンテンツのぼやけを防ぐために、キャンバスグリッドをデバイスピクセル比率に合わせてサイズ設定してください。

const observer = new ResizeObserver(([entry]) => {
  const dpc = entry.devicePixelContentBoxSize;
  canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio);
  canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio);
});

const supportsDevicePixelContentBox =
  typeof ResizeObserverEntry !== 'undefined' &&
  'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);
ステップ2: レンダリング

2Dコンテキストの場合、drawElementImageメソッドを使用します。これは、要素が再描画されるたびに(たとえば、テキストのハイライト中やユーザー入力時)トリガーされるpaintイベント内で実行します。インタラクティビティが継続して機能するように、レンダリング呼び出しから返された変換を要素のCSSトランスフォームに更新することが重要です。

const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');

canvas.onpaint = () => {
  ctx.reset();

  // フォーム要素を x:0, y:0 に描画
  let transform = ctx.drawElementImage(form_element, 0, 0);

  // 後で返された変換を使用...
};
WebGLでのレンダリング

WebGLでは、texElementImage2Dを使用します。これはtexImage2Dと似ていますが、DOM要素をソースとして受け取ります。

canvas.onpaint = () => {
  if (gl.texElementImage2D) {
    gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
  }
};
WebGPUでのレンダリング

WebGPUは、デバイスキューのcopyElementImageToTextureメソッドを使用します。これはcopyExternalImageToTextureに似ています。

canvas.onpaint = () => {
  root.device.queue.copyElementImageToTexture(
    valueElement,
    { texture: targetTexture }
  );
};
ステップ3: CSSトランスフォームの更新

要素をキャンバスにレンダリングしたので、ブラウザにその位置を通知する必要があります。これにより、キャンバスとDOMのレイアウト間の空間的な同期が保証されます。これは、イベントゾーン(ユーザーがクリックまたはホバーする正確な場所)を要素がレンダリングされている場所と正しくマッピングするために重要です。

2Dコンテキストの場合、レンダリング呼び出しから返された変換を.style.transformプロパティに適用します。

const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');

canvas.onpaint = () => {
  ctx.reset();
  // フォーム要素を x:0, y:0 に描画
  let transform = ctx.drawElementImage(form_element, 0, 0);

  // DOMの位置を描画された位置と同期
  form_element.style.transform = transform.toString();
};

WebGLまたはWebGPUの場合、要素の画面上の位置は、シェーダーコードで出力テクスチャがどのように使用されるかに依存し、キャンバスレンダリングコンテキストから推測することはできません。ただし、シェーダープログラムが典型的なモデルビュープロジェクションを使用してテクスチャを描画する場合、新しい便利な関数element.getElementTransform()を使用して、drawElementImage()の戻り値と同様に使用できる変換を計算できます。これを容易にするために、次の手順を実行する必要があります。

  • WebGL MVP MatrixをDOM Matrixに変換します。
  • HTML要素を正規化します。 HTML要素はピクセル単位でサイズ設定されます(例: 幅200px)。しかし、WebGLは通常、オブジェクトを「単位正方形」(たとえば、0から1の範囲)として扱います。正規化しないと、200pxのボタンが200倍大きく見えます。
  • キャンバスビューポートにマッピングします。 このステップは「スケーリング」フェーズです。この単位空間の数学を、画面上の<canvas>要素の実際のピクセル寸法に一致するように引き伸ばします。また、WebGLでは上方向が正ですが、CSSでは下方向が正であるため、Y軸を反転させます。
  • 最終的な変換を計算します。 行列を次の順序で乗算します: Viewport * MVP * Normalization. これらを1つの最終的な変換にまとめることで、そのHTML要素レイヤーが3D描画に合わせるためにどこに配置されるべきかをブラウザに正確に伝える「マップ」が生成されます。
  • 変換をHTML要素に適用します。 これにより、HTML要素レイヤーがレンダリングされたピクセルに直接重なるように配置されます。これにより、ユーザーがボタンをクリックしたりテキストを選択したりするときに、実際のHTML要素が操作されることが保証されます。
if (canvas.getElementTransform) {
  // 1. WebGL MVP Matrix を DOM Matrix に変換
  const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));

  // 2. HTML要素を正規化 (ピクセル -> 1x1 単位正方形)
  const width = targetHTMLElement.offsetWidth;
  const height = targetHTMLElement.offsetHeight;

  const cssToUnitSpace = new DOMMatrix()
    .scale(1 / width, -1 / height, 1) // 単位サイズに縮小し、Y軸を反転
    .translate(-width / 2, -height / 2); // 要素を中央揃え

  // 3. キャンバスビューポートにマッピング
  const clipToCanvasViewport = new DOMMatrix()
    .translate(canvas.width / 2, canvas.height / 2) // 原点を中央に移動
    .scale(canvas.width / 2, -canvas.height / 2, 1); // キャンバス寸法に引き伸ばす

  // 4. 乗算: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
  const screenSpaceTransform = clipToCanvasViewport
      .multiply(mvpDOM)
      .multiply(cssToUnitSpace);

  // 5. 変換に適用
  const computedTransform = canvas.getElementTransform(targetHTMLElement, screenSpaceTransform);
  if (computedTransform) {
    targetHTMLElement.style.transform = computedTransform.toString();
  }
}

ライブラリとフレームワークのサポート

一部の人気ライブラリは、HTML-in-Canvas機能をすでにサポートしています。

Three.js

行列を手動で更新するのは手間がかかるため、フレームワークはすでに導入を進めています。Three.jsには、新しいTHREE.HTMLTextureを使用した実験的なサポートがあります。

const material = new THREE.MeshBasicMaterial();
material.map = new THREE.HTMLTexture(uiElement); // DOM要素を渡す

const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
PlayCanvas

PlayCanvasも、テクスチャAPIを使用してHTML-in-Canvasをサポートしています。

// ソースを設定するために 'paint' イベントを待つ
canvas.addEventListener('paint', () => {
    htmlTexture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();

// 更新を続ける
canvas.addEventListener('paint', onPaintUpload);

const material = new pc.StandardMaterial();
material.diffuseMap = htmlTexture;
material.update();

デモ

デモを試す前に、環境が正しく設定されていることを確認してください。

APIの使用方法の参考となるいくつかのデモがあります。すでに、翻訳可能な3D書籍からガラスシェーダーを通して屈折するUI要素まで、コミュニティによる創造的なソリューションを目にしています。

  • 3Dブック: HTMLレイアウトを使用したWebGLレンダリングの3Dブック。ユーザーはCSSでフォントを切り替えられます。DOMベースであるため、組み込み翻訳が即座に機能し、AIエージェントはより少ない複雑さでテキストを抽出できます。
  • インタラクティブな3D UI: 3Dモデルに基づいて光を屈折させるWebGPUゼリースライダーですが、標準のHTML<input type="range">ステップ属性にも応答します。
  • アニメーションテクスチャ: カスタムアニメーションループを必要とせずに、DOMを直接WebGLテクスチャにレンダリングする、アニメーションSVG鉛筆を描画する動的な3Dビルボード。
  • 屈折オーバーレイ: 移動する3Dカーソルによって歪められるインタラクティブなタイポグラフィレイヤーですが、ページ内検索を使用して完全に選択および検索可能です。

コミュニティによって作成されたデモのコレクションをチェックしてください。このコレクションでHTML-in-Canvasデモを特集したい場合は、プルリクエストを作成して追加してください。

制限事項

強力ですが、APIにはいくつかの意図的な制限があります。

  • クロスオリジンコンテンツ: セキュリティとプライバシー上の理由から、APIはクロスオリジンiframeコンテンツでは機能しません。
  • メインスレッドスクロール: HTML-in-canvasはJavaScriptで描画されるため、スクロールやアニメーションは、キャンバス外のようにJavaScriptとは独立して更新できません。開発者は、スクロールコンテンツをキャンバス内に配置する場合と、キャンバス全体をスクロールする場合のパフォーマンス特性を慎重に検討する必要があります。

フィードバック

HTML-in-Canvas APIを試している場合は、ぜひご意見をお聞かせください!オリジン試用版にサインアップして、実験段階でサイトでこの機能を有効にし、API設計の形成にご協力いただけます。また、問題を報告してフィードバックを提供することもできます。

リソース