Mises à jour HTML-in-Canvas : vers une meilleure API Web

Chrome 150 et 155 ont introduit plusieurs modifications dans l'API HTML-in-Canvas. Ces modifications ont été apportées en réponse aux commentaires de la communauté et aux normes, dans le but d'améliorer l'ergonomie de l'API. Cet article de blog décrit les modifications et explique comment vous assurer que votre implémentation est à jour.

Les mises à jour incluent le renommage des attributs et des méthodes, l'obligation de pré-allouer explicitement la mémoire de texture et la mise à jour des modèles de synchronisation DOM. De plus, la phase d'évaluation de l'origine HTML dans le canevas est désormais prolongée. Vous trouverez ci-dessous une explication des modifications apportées, des raisons de ces modifications et de la façon de mettre à jour votre codebase.

Modifications apportées à Chrome 150

Dans Chrome 150, nous avons modifié les signatures d'arguments dans WebGL et WebGPU pour éliminer la redondance et nous aligner sur les conventions graphiques modernes.

WebGL

Nous avons supprimé les arguments redondants (level, srcFormat, destType) de texElementImage2D (pour en savoir plus, consultez la discussion sur 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

Nous avons standardisé les paramètres copyElementImageToTexture dans les objets sourceDict et destDict (voir 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);

Modifications apportées à Chrome 155

Chrome 155 apporte d'autres améliorations basées sur les commentaires des développeurs. Nous mettons à jour les noms de méthodes, exigeons une pré-allocation explicite de la mémoire de texture, prenons en charge les sous-arborescences DOM complexes et découplons la synchronisation DOM.

WebGPU

La méthode WebGPU a été renommée copyElementImageToTexture en drawElementImageToTexture. En plus du changement de nom, les coordonnées des paramètres sont développées (sourceX, sourceWidth) et les limites de destination sont regroupées dans un objet size (GPUExtent3D) standard :

// 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

Les modifications apportées à WebGL pour HTML-in-Canvas concernent trois aspects :

  • Le nom de la méthode passe de texElementImage2D à texElementSubImage2D.
  • La signature de la méthode change. Plus précisément, elle ajoute les arguments xoffset et yoffset pour permettre éventuellement de spécifier un décalage dans la texture de destination à laquelle l'élément doit être dessiné.
  • La méthode n'alloue plus automatiquement de stockage tampon pour la texture de destination. Le stockage doit être alloué explicitement avant d'utiliser l'API. Cela ressemble au comportement de 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
);

Attributs Canvas et éléments enfants Drawable

Nous modifions la façon dont vous préparez votre canevas pour afficher le code HTML.

Remplacement du nom de layoutsubtree par content="drawable"

Pour s'aligner sur les conventions de dénomination des attributs HTML, l'attribut booléen layoutsubtree de l'élément <canvas> a été renommé content="drawable" (pour en savoir plus, consultez le problème 169 du WICG).

Attribut drawable pour les descendants

Auparavant, seuls les enfants directs d'un canevas étaient affichés. Pour prendre en charge les hiérarchies DOM complexes sans aplatir le balisage, vous pouvez désormais ajouter explicitement l'attribut drawable à n'importe quel élément descendant que vous souhaitez dessiner indépendamment :

<!-- 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>

Un élément avec drawable capture l'intégralité de son sous-arbre sauf les descendants imbriqués qui spécifient également drawable. Cela permet d'animer et de dessiner les conteneurs parents et les éléments enfants tels que les boutons dans des appels de dessin distincts.

Synchronisation des tests de sélection

Auparavant, les développeurs devaient capturer la valeur renvoyée par les méthodes de dessin et la transmettre à element.style.transform. La méthode drawElementImage() renvoie désormais void, et la synchronisation dépend du contexte.

Contextes 2D : synchronisation automatique

Dans le canevas 2D, le navigateur synchronise automatiquement les limites de test de sélection DOM et les anneaux de sélection du lecteur d'écran avec les coordonnées dessinées. Vous pouvez supprimer tous les pipes de transformation manuels :

// 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

Pour gérer manuellement la géométrie en 2D, transmettez { preserveElementGeometry: true } à drawElementImage() et appliquez des transformations à l'aide de canvas.updateElementGeometry().

Contextes 3D : explicite updateElementGeometry()

Pour synchroniser le DOM dans les contextes WebGL ou WebGPU, créez une transformation DOMMatrix et transmettez-la à 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 });
});

Compatibilité entre les versions

Si votre application doit prendre en charge les utilisateurs sur plusieurs versions de Chrome pendant la période de transition, vérifiez la présence des méthodes mises à jour sur leurs prototypes et ajustez votre implémentation en conséquence.

Assurez-vous de définir les attributs d'élément <canvas> corrects pour chaque version de navigateur :

// ============================================================================
//  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', '');
  }
}

Pour le contexte 2D, inspectez la valeur renvoyée de drawElementImage() et vérifiez les attributs du prototype de canevas pour prendre en charge les deux versions :

// ============================================================================
// 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 = "";
    }
  }
}

Pour WebGPU, utilisez la signature et le nom de méthode compatibles :

// ============================================================================
// 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 });
  }
}

Vérifiez la compatibilité de votre navigateur avec 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);
}

Extension Origin Trial

La phase d'évaluation HTML-in-Canvas a été prolongée jusqu'à Chrome 160. Veillez à renouveler votre jeton d'essai Origin Trial pour continuer à proposer la fonctionnalité HTML dans le canevas à vos utilisateurs. Pour en savoir plus, consultez l'article sur les versions d'essai de l'origine.

Envoyer des commentaires

Faites-nous part de vos commentaires sur ces mises à jour dans vos applications en signalant un problème dans le dépôt GitHub WICG. Abonnez-vous à la newsletter pour les développeurs pour rester informé des dernières actualités concernant HTML-in-Canvas.