HTML-in-Canvas のアップデート: より優れたウェブ API に向けてのイテレーション

Chrome 150 と 155 では、HTML-in-Canvas API にいくつかの変更が加えられました。これらの変更は、API の人間工学を改善することを目的として、コミュニティと標準のフィードバックに対応したものです。このブログ投稿では、変更点と、実装が最新の状態であることを確認する方法について説明します。

更新には、属性とメソッドの名前変更、テクスチャ メモリの明示的な事前割り当ての要求、DOM 同期モデルの更新が含まれます。また、 HTML-in-Canvas オリジン トライアルが 延長されました。以下では、変更内容、変更の理由、コードベースの更新方法について説明します。

Chrome 150 の変更点

Chrome 150 では、冗長性を排除し、最新のグラフィック規約に沿うように、WebGL と WebGPU の引数シグネチャを変更しました。

WebGL

texElementImage2D から冗長な引数(level、srcFormat、destType)を削除しました(詳しくは WebGL のディスカッションを参照)。

// Old implementation (Chrome < 150)
const level = 0;
const internalFormat = gl.RGBA;
const srcFormat = gl.RGBA;
const destType = gl.UNSIGNED_BYTE;

gl.texElementImage2D(
  gl.TEXTURE_2D,
  level,
  internalFormat,
  srcFormat,
  destType,
  element
);

// New implementation (from Chrome 150)
const internalFormat = gl.RGBA8;
gl.texElementImage2D(gl.TEXTURE_2D, internalFormat, element);

WebGPU

copyElementImageToTexture パラメータを sourceDict オブジェクトと destDict オブジェクトに標準化しました(GPUWeb PR#6250 を参照)。

// Old implementation (Chrome < 150)
GPUQueue.copyElementImageToTexture(
  element,
  width,
  height,
  { texture: texture }
);

// New implementation (from Chrome 150)
const sourceDict = { source: element };
const destDict = {
  destination: { texture: texture },
  width: width,
  height: height
};

GPUQueue.copyElementImageToTexture(sourceDict, destDict);

Chrome 155 の変更点

Chrome 155 では、デベロッパーからのフィードバックに基づいてさらに改善が加えられています。メソッド名の更新、テクスチャ メモリの明示的な事前割り当ての要求、複雑な DOM サブツリーのサポート、DOM 同期を分離します。

WebGPU

WebGPU メソッドの名前を copyElementImageToTexture から drawElementImageToTexture に変更しました。名前の変更とともに、パラメータ座標が拡張され(sourceX、sourceWidth)、宛先の境界が標準の size(GPUExtent3D)オブジェクトにグループ化されます。

// BEFORE (Chrome 150)
const sourceDict = {
  source: myElement,
  sx: 0,
  sy: 0,
  swidth: 100,
  sheight: 100
};
const destDict = {
  destination: { texture: myTexture },
  width: 300,
  height: 200
};

queue.copyElementImageToTexture(sourceDict, destDict);

// AFTER (Chrome 155+)
const sourceDict = {
  source: myElement,
  sourceX: 0,
  sourceY: 0,
  sourceWidth: 100,
  sourceHeight: 100
};
const destDict = {
  texture: myTexture,
  size: { width: 300, height: 200 }
};

queue.drawElementImageToTexture(sourceDict, destDict);

WebGL

HTML-in-Canvas の WebGL の変更には、次の 3 つの側面があります。

  • メソッド名が texElementImage2D から texElementSubImage2D に変更されます。
  • メソッド シグネチャが変更されます。最も重要な変更点は、要素を描画する宛先テクスチャのオフセットをオプションで指定できるように、xoffset 引数と yoffset 引数が追加されることです。
  • このメソッドは、宛先テクスチャのバッファ ストレージを自動的に割り当てなくなりました。API を使用する前に、ストレージを明示的に割り当てる必要があります。これは texSubImage2D の動作と似ています。
// 1. Capture natural layout size of the element
const elementImage = canvas.captureElementImage(element);

// 2. Pre-allocate texture backing once (or when element resizes)
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA8,
  Math.ceil(elementImage.width),
  Math.ceil(elementImage.height),
  0,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  null // null reserves VRAM without uploading pixels
);

// 3. Upload DOM into pre-allocated VRAM
gl.texElementSubImage2D(
  gl.TEXTURE_2D,
  0,                  // level
  0,                  // xoffset
  0,                  // yoffset
  element             // pass DOM element
);

Canvas の属性とドローアブルの子要素

HTML をレンダリングするためのキャンバスの準備方法を更新しています。

layoutsubtree を content="drawable" に名前を変更

HTML 属性の命名規則に合わせるため、<canvas> 要素のブール値 layoutsubtree 属性の名前が content="drawable" に変更されました(詳細については、WICG Issue #169 をご覧ください)。

子孫の drawable 属性

以前は、キャンバスの直接の子のみがレンダリングされていました。マークアップをフラット化せずに複雑な DOM 階層をサポートするために、独立して描画するすべての子孫要素に drawable 属性を明示的に追加できるようになりました。

<!-- BEFORE -->
<canvas layoutsubtree>
  <div id="badge">
    <h2>Player Stats</h2>
    <button id="actionBtn">Equip</button>
  </div>
</canvas>

<!-- AFTER (Chrome 155+) -->
<canvas content="drawable">
  <!-- Subtree A (Body and heading) -->
  <div id="badge" drawable>
    <h2>Player Stats</h2>
    <!-- Subtree B (Excluded from Subtree A; drawn and animated independently) -->
    <button id="actionBtn" drawable>Equip</button>
  </div>
</canvas>

drawable を持つ要素は、drawable を指定するネストされた子孫を除き、サブツリー全体をキャプチャします。これにより、親コンテナとボタンなどの子要素を別々の描画呼び出しでアニメーション化して描画できます。

ヒットテストの同期

以前は、デベロッパーは描画メソッドから戻り値を取得し、それを element.style.transform にパイプする必要がありました。drawElementImage() メソッドは void を返すようになり、同期はコンテキストに依存するようになりました。

2D コンテキスト: 自動同期

2D キャンバスでは、ブラウザは DOM ヒットテストの境界とスクリーン リーダーのフォーカス リングを描画された座標に自動的に同期します。手動変換パイプラインをすべて削除できます。

// BEFORE
const transform = ctx.drawElementImage(element, x, y);
element.style.transform = transform.toString();

// AFTER (Chrome 155+)
ctx.drawElementImage(element, x, y);
// No style update needed: position & hit-testing sync automatically

2D でジオメトリを手動で管理するには、{ preserveElementGeometry: true } を drawElementImage() に渡し、canvas.updateElementGeometry() を使用して変換を適用します。

3D コンテキスト: 明示的 updateElementGeometry()

WebGL または WebGPU コンテキストで DOM を同期するには、DOMMatrix 変換を構築して canvas.updateElementGeometry() に渡します。

canvas.addEventListener('paint', () => {
  // 1. Upload element snapshot to GPU texture
  device.queue.drawElementImageToTexture({ source: element }, { texture });

  // 2. Render 3D scene...
  drawScene();

  // 3. Construct transform matrix as needed
  const canvasTransform = new DOMMatrix().translate(x, y);

  // 4. Update DOM hit-testing and accessibility bounding boxes
  canvas.updateElementGeometry(element, { canvasTransform });
});

クロス バージョンの互換性

移行期間中にアプリケーションで複数の Chrome バージョンのユーザーをサポートする必要がある場合は、プロトタイプで更新されたメソッドの存在を確認し、実装を適宜調整してください。

すべてのブラウザ バージョンで <canvas> 要素の属性が正しく設定されていることを確認してください。

// ============================================================================
//  Cross-version canvas markup
// ============================================================================
if (!canvas.hasAttribute('content') && !canvas.hasAttribute('layoutsubtree')) {
  if ('content' in HTMLCanvasElement.prototype) {
    // Chrome 155+ (New standardized attribute)
    canvas.setAttribute('content', 'drawable');
  } else {
    // Chrome < 155 (Legacy boolean attribute)
    canvas.setAttribute('layoutsubtree', '');
  }
}

2D コンテキストの場合、drawElementImage() の戻り値を検査し、キャンバス プロトタイプ属性をチェックして、両方のバージョンをサポートします。

// ============================================================================
// Cross-version 2D drawing & synchronization
// ============================================================================
function draw2DElement(ctx, element, x, y) {
  // Execute the 2D draw call
  const result = ctx.drawElementImage(element, x, y);

  if (result !== undefined && typeof result?.toString === 'function') {
    // Chrome < 155: drawElementImage returned a DOMMatrix
    // Requires manual transform piping to align DOM hit-test bounds:
    element.style.transform = result.toString();
  } else {
    // Chrome 155+: drawElementImage returns void / undefined
    // The browser automatically aligns DOM positioning, clicks, and a11y bounds!
    // If you previously had element.style.transform logic here, clear it:
    if (element.style.transform) {
      element.style.transform = "";
    }
  }
}

WebGPU の場合は、互換性のあるメソッドのシグネチャと名前を使用します。

// ============================================================================
// WebGPU cross-version support
// ============================================================================
if ('drawElementImageToTexture' in GPUQueue.prototype) {
  // Chrome 155+ (Latest implementation)
  queue.drawElementImageToTexture(
    { source: element, sourceX: 0, sourceY: 0, sourceWidth: w, sourceHeight: h },
    { texture: myTexture, size: { width: w, height: h } }
  );
} else if (typeof queue.copyElementImageToTexture === "function") {
  // Chrome 150 - 154: Try the Chrome 150+ dictionary signature first,
  // then fall back to the Chrome < 150 signature
  try {
    queue.copyElementImageToTexture(
      { source: element },
      { destination: { texture: myTexture }, width: w, height: h }
    );
  } catch (err) {
    // Chrome < 150
    queue.copyElementImageToTexture(element, { texture: myTexture });
  }
}

WebGL のブラウザの互換性を確認してください。

// ============================================================================
// WebGL cross-version support
// ============================================================================
if ('texElementSubImage2D' in WebGL2RenderingContext.prototype) {
  // Chrome 155+ (Explicit pre-allocation + sub-image)
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, element);
} else if (gl.texElementImage2D && gl.texElementImage2D.length === 3) {
  // Chrome 150-154 (3-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, element);
} else if (gl.texElementImage2D) {
  // Chrome < 150 (6-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, element);
}

オリジン トライアルの延長

HTML-in-Canvas オリジン トライアルは Chrome 160 まで延長されました。ユーザーに HTML-in-Canvas 機能を提供し続けるには、オリジントライアル トークンを更新してください。詳しくは、オリジン トライアルに関する記事をご覧ください。

フィードバックをお寄せください

これらの更新がアプリケーションでどのように機能するかについては、 WICG GitHub リポジトリに問題を報告してください。デベロッパー ニュースレターに登録して、HTML-in-Canvas の最新情報を入手しましょう。