Canvas 中的 HTML 更新:逐步改善 Web API

Chrome 150 和 155 對 Canvas 中的 HTML 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 變更包含三個層面:

  • 方法名稱從 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
);

畫布屬性和可繪製的子項元素

我們將更新準備畫布以算繪 HTML 的方式。

將「layoutsubtree」重新命名為「content="drawable"」

為符合 HTML 屬性命名慣例,<canvas> 元素中的布林值 layoutsubtree 屬性已重新命名為 content="drawable" (如需詳細討論,請參閱 WICG 問題 #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。請務必續訂來源試用權杖,繼續為使用者提供 Canvas 中的 HTML 功能。詳情請參閱原始碼試用文章。

提供意見

請 在 WICG GitHub 存放區中回報問題,讓我們瞭解這些更新在您應用程式中的運作情形。訂閱開發人員電子報,隨時掌握 HTML-in-Canvas 的最新消息。