Pendant des années, les développeurs Web ont dû faire un choix architectural difficile lorsqu'ils créaient des applications visuelles complexes et très interactives sur le Web : s'appuyer sur le DOM pour ses riches fonctionnalités sémantiques ou effectuer le rendu directement sur l'élément <canvas> pour des performances graphiques de bas niveau ?
Avec la nouvelle API HTML-in-Canvas expérimentale, disponible dès maintenant en essai Origin Trial, vous n'avez plus à choisir. Cette API vous permet de dessiner du contenu DOM directement dans un canevas 2D ou une texture WebGL/WebGPU tout en conservant l'interactivité et l'accessibilité de l'UI, et en la connectant à vos fonctionnalités de navigateur préférées. En combinant HTML et traitement graphique de bas niveau, vous pouvez créer des expériences qui étaient auparavant impossibles.
Le DOM par rapport à Canvas
Pour comprendre la puissance de cette nouvelle API, il est utile d'examiner les forces relatives du DOM et du Canvas.
Le DOM est l'élément de base de l'UI Web. Il propose des solutions de mise en page de texte prêtes à l'emploi, en utilisant du contenu compris sémantiquement pour créer des interfaces riches. Cela permet aux utilisateurs d'effectuer des opérations courantes sur les pages Web de manière fluide, comme mettre en surbrillance du texte à copier ou effectuer un clic droit sur une image pour l'enregistrer. Le DOM s'intègre également aux fonctionnalités essentielles du navigateur : outils d'accessibilité, traduction, recherche sur la page, mode Lecture, extensions, mode sombre, zoom du navigateur et saisie semi-automatique.
Canvas (et WebGL/WebGPU), en revanche, permet un accès de bas niveau pour piloter une grille de pixels pour des graphiques 2D et 3D très avancés. Les jeux et les applications Web complexes (comme Google Docs ou Figma) nécessitent cet accès performant de bas niveau. Comme le canevas est fondamentalement une grille de pixels, la prise en charge de fonctionnalités telles que le texte responsif nécessitait une logique d'UI personnalisée complexe, ce qui augmentait considérablement la taille de votre bundle. Surtout, toutes les puissantes fonctionnalités du navigateur intégrées au DOM sont complètement inutilisables lorsque l'UI est piégée dans une grille de pixels de canevas statique.
Avantages de l'intégration du DOM à Canvas
L'API HTML-in-Canvas est le pont qui vous offre le meilleur des deux mondes. En plaçant le code HTML dans l'élément <canvas> et en synchronisant sa transformation, vous vous assurez que le contenu reste entièrement interactif et que toutes les intégrations du navigateur fonctionnent automatiquement.
Voici ce que vous obtenez en laissant le DOM gérer votre UI dans un élément <canvas> :
- Mise en page et mise en forme du texte : mise en page et mise en forme du texte simplifiées, y compris le texte multiligne ou bidirectionnel avec des styles CSS appliqués.
- Commandes de formulaire : commandes de formulaire expressives et plus faciles à utiliser, avec de nombreuses options de personnalisation.
- Sélection de texte, copier/coller et clic droit : les utilisateurs peuvent mettre en surbrillance du texte dans vos scènes 3D ou utiliser les menus contextuels accessibles par clic droit de manière native.
- Sélection de texte, copier/coller et clic droit : les utilisateurs peuvent mettre en surbrillance du texte dans vos scènes 3D ou utiliser les menus contextuels accessibles par clic droit de manière native.
- Accessibilité : le contenu affiché dans le canevas est exposé à l'arborescence d'accessibilité. Les systèmes d'accessibilité peuvent analyser l'UI comme ils le font pour le code HTML normal et l'exposer à des systèmes tels que les lecteurs d'écran.
- Find-in-page: : les utilisateurs peuvent utiliser la fonctionnalité Rechercher sur la page (Ctrl/Cmd+F) pour rechercher du texte. Le navigateur le mettra en surbrillance directement dans vos textures WebGL.
- Find-in-page: : les utilisateurs peuvent utiliser la fonctionnalité Rechercher sur la page (Ctrl/Cmd+F) pour rechercher du texte. Le navigateur le mettra en surbrillance directement dans vos textures WebGL.
- Indexabilité et compatibilité avec les agents d'IA : les robots d'exploration Web et les agents d'IA peuvent indexer et lire facilement le texte affiché dans vos scènes 2D et 3D.
- Intégration des extensions : les extensions de navigateur fonctionnent de manière native. Par exemple, une extension de remplacement de texte mettra automatiquement à jour le texte affiché sur vos maillages 3D.
- Intégration des outils de développement : vous pouvez inspecter le contenu de votre canevas, y compris les éléments d'interface utilisateur WebGL/WebGPU, directement dans les outils pour les développeurs Chrome. Modifiez un style CSS dans l'inspecteur et voyez-le se mettre à jour instantanément sur la texture 3D.
Cas d'utilisation généraux
Cette API offre un potentiel incroyable dans plusieurs domaines :
- Grandes applications basées sur le canevas : les applications Web lourdes telles que Google Docs, Miro ou Figma peuvent désormais afficher des composants d'interface utilisateur complexes de manière native dans leurs espaces de travail basés sur le canevas, ce qui améliore l'accessibilité et réduit le poids du bundle.
- Scènes et jeux 3D : les sites marketing, les expériences WebXR immersives et les jeux Web peuvent désormais placer des UI Web entièrement interactives dans des scènes 3D, comme un livre 3D qui utilise du texte DOM réel ou un terminal de jeu qui prend en charge le copier-coller de manière native.
Utiliser l'API
L'utilisation de l'API se déroule en trois phases : la configuration du canevas, le rendu dans le canevas et la mise à jour de la transformation CSS afin que le navigateur sache où l'élément se trouve physiquement à l'écran.
Prérequis
L'API HTML-in-Canvas est en phase d'essai Origin Trial dans Chrome 148 à 150. Pour le tester sur votre site, utilisez Chrome Canary 149 ou version ultérieure avec l'indicateur chrome://flags/#canvas-draw-element activé. Pour activer l'API pour d'autres utilisateurs, inscrivez-vous à la phase d'évaluation de l'origine.
Étape 1 : Configuration de base de Canvas
Commencez par ajouter l'attribut layoutsubtree à votre balise <canvas>. Le navigateur est ainsi informé du contenu imbriqué dans le canevas, ce qui le prépare à l'afficher dans le canevas et à l'exposer aux arbres d'accessibilité.
<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>
Dimensionner la grille du canevas
Pour éviter que le contenu rendu ne soit flou, assurez-vous de dimensionner la grille du canevas en fonction du facteur d'échelle de l'appareil.
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);
Étape 2 : Rendu
Pour un contexte 2D, utilisez la méthode drawElementImage. Faites-le à l'intérieur de l'événement paint, qui se déclenche chaque fois que l'élément est redessiné, par exemple lors de la mise en surbrillance du texte ou de l'entrée utilisateur. Il est essentiel de mettre à jour la transformation CSS de l'élément avec la valeur renvoyée pour que l'interactivité continue de fonctionner.
const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Use the transform returned later on...
};
Effectuer le rendu avec WebGL
Pour WebGL, vous utilisez texElementImage2D. Il fonctionne de la même manière que texImage2D, mais prend l'élément DOM comme source.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Effectuer le rendu avec WebGPU
WebGPU utilise la méthode copyElementImageToTexture sur la file d'attente de l'appareil, de la même manière que copyExternalImageToTexture :
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Étape 3 : Mettez à jour la transformation CSS
Maintenant que vous avez affiché l'élément dans le canevas, vous devez indiquer au navigateur où il se trouve. Cela garantit la synchronisation spatiale entre le canevas et la mise en page du DOM. C'est important pour que le navigateur puisse mapper correctement la zone d'événement (par exemple, l'endroit exact où l'utilisateur clique ou pointe) avec l'endroit où l'élément est affiché.
Dans le cas du contexte 2D, appliquez la transformation renvoyée par l'appel de rendu à .style.transform property :
const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Sync the DOM location with the drawn location
form_element.style.transform = transform.toString();
};
Avec WebGL ou WebGPU, l'emplacement à l'écran d'un élément dépend de la façon dont la texture de sortie est utilisée par le code du nuanceur. Il ne peut pas être déduit du contexte de rendu du canevas. Toutefois, si votre programme de nuanceur utilise une projection de vue de modèle typique pour dessiner la texture, vous pouvez utiliser la nouvelle fonction pratique element.getElementTransform() pour calculer une transformation qui peut être utilisée de la même manière que la valeur renvoyée par drawElementImage(). Pour faciliter cette opération, procédez comme suit :
- Convertissez la matrice MVP WebGL en matrice DOM.
- Normalisez l'élément HTML. Les éléments HTML sont dimensionnés en pixels (par exemple, 200 px de large). Toutefois, WebGL traite généralement les objets comme des "carrés unitaires", par exemple, allant de 0 à 1. Si vous ne normalisez pas, votre bouton de 200 px aura l'air 200 fois plus grand.
- Mappez à la fenêtre d'affichage du canevas. Cette étape correspond à la phase de "remise à l'échelle" : elle étire à nouveau les calculs d'espace unitaire pour qu'ils correspondent aux dimensions réelles en pixels de votre élément
<canvas>à l'écran. Il inverse également l'axe Y, car dans WebGL, le haut est positif, mais dans CSS, le bas est positif. - Calculez la transformation finale. Multipliez les matrices dans l'ordre :
Viewport * MVP * Normalization.. En les combinant en une seule transformation finale, vous obtenez une "carte" qui indique au navigateur exactement où la couche d'élément HTML doit se trouver pour s'aligner sur le dessin 3D. - Appliquez la transformation à l'élément HTML. La couche de l'élément HTML est ainsi placée directement au-dessus de ses pixels rendus. Ainsi, lorsqu'un utilisateur clique sur un bouton ou sélectionne du texte, il interagit avec le véritable élément HTML.
if (canvas.getElementTransform) {
// 1. Convert WebGL MVP Matrix to DOM Matrix
const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));
// 2. Normalize the HTML element (pixels -> 1x1 unit square)
const width = targetHTMLElement.offsetWidth;
const height = targetHTMLElement.offsetHeight;
const cssToUnitSpace = new DOMMatrix()
.scale(1 / width, -1 / height, 1) // Shrink to unit size and flip Y
.translate(-width / 2, -height / 2); // Center the element
// 3. Map to the canvas viewport
const clipToCanvasViewport = new DOMMatrix()
.translate(canvas.width / 2, canvas.height / 2) // Move origin to center
.scale(canvas.width / 2, -canvas.height / 2, 1); // Stretch to canvas dimensions
// 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
const screenSpaceTransform = clipToCanvasViewport
.multiply(mvpDOM)
.multiply(cssToUnitSpace);
// 5. Apply to the transform
const computedTransform = canvas.getElementTransform(targetHTMLElement, screenSpaceTransform);
if (computedTransform) {
targetHTMLElement.style.transform = computedTransform.toString();
}
}
Compatibilité avec les bibliothèques et les frameworks
Certaines bibliothèques populaires sont déjà compatibles avec la fonctionnalité HTML dans le canevas.
Three.js
La mise à jour manuelle des matrices peut être fastidieuse. C'est pourquoi les frameworks s'y mettent déjà. Three.js est compatible en version expérimentale avec le nouveau THREE.HTMLTexture :
const material = new THREE.MeshBasicMaterial();
material.map = new THREE.HTMLTexture(uiElement); // Pass the DOM element
const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
PlayCanvas
PlayCanvas est également compatible avec HTML-in-Canvas à l'aide de son API de texture :
// Wait for the 'paint' event to set the source
canvas.addEventListener('paint', () => {
htmlTexture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();
// Keep up to date
canvas.addEventListener('paint', onPaintUpload);
const material = new pc.StandardMaterial();
material.diffuseMap = htmlTexture;
material.update();
Démonstrations
Avant d'essayer les démonstrations, assurez-vous que votre environnement est correctement configuré.
Plusieurs démonstrations servent de référence pour l'utilisation de l'API. Nous avons déjà vu des solutions créatives de la part de la communauté, allant des livres 3D traduisibles aux éléments d'interface utilisateur qui se réfractent à travers les nuanceurs de verre :
- Livre 3D : livre 3D rendu avec WebGL et dont les pages sont mises en page en HTML. Les utilisateurs peuvent remplacer les polices avec CSS. Comme elle est basée sur le DOM, la traduction intégrée fonctionne instantanément et les agents d'IA peuvent extraire le texte plus facilement.
- Interfaces utilisateur 3D interactives : un curseur WebGPU qui réfracte la lumière en fonction d'un modèle 3D sous-jacent, tout en répondant aux attributs de pas
<input type="range">HTML standards. - Textures animées : un panneau 3D dynamique affichant un crayon SVG animé à l'aide du DOM directement dans une texture WebGL sans avoir besoin d'une boucle d'animation personnalisée.
- Calques réfractifs : calque typographique interactif déformé par un curseur 3D en mouvement, mais entièrement sélectionnable et consultable à l'aide de la fonctionnalité Rechercher sur la page.
Découvrez la collection de démos créées par la communauté. Si vous souhaitez que votre démo HTML-in-Canvas figure dans cette collection, créez une demande d'extraction pour l'ajouter.
Limites
Bien qu'efficace, l'API présente quelques limites volontaires :
- Contenu d'origine croisée : pour des raisons de sécurité et de confidentialité, l'API ne fonctionne pas avec le contenu d'iframe d'origine croisée.
- Défilement du thread principal : le code HTML dans le canevas est dessiné avec JavaScript, ce qui signifie que le défilement et les animations ne peuvent pas être mis à jour indépendamment de JavaScript, comme c'est le cas en dehors du canevas. Les développeurs doivent examiner attentivement les caractéristiques de performances de l'affichage de contenu défilant dans le canevas par rapport au défilement de l'intégralité du canevas.
Commentaires
Si vous testez l'API HTML-in-Canvas, n'hésitez pas à nous en parler. Vous pouvez vous inscrire à l'essai Origin Trial pour activer la fonctionnalité sur votre site pendant la phase expérimentale et nous aider à concevoir l'API. Vous pouvez également signaler un problème pour nous faire part de vos commentaires.
Ressources
- Prise en charge d'HTML-in-Canvas dans Three.js
- Démonstration de HTML-in-Canvas dans Three.js
- Compatibilité HTML-in-Canvas dans PlayCanvas : documentation pour les développeurs
- Démonstration de HTML-in-Canvas dans PlayCanvas
- HTML-in-Canvas : explication
- Conseils sur le Web moderne pour les outils de codage par IA pour HTML-in-Canvas
- Démonstrations Chrome.dev pour HTML-in-Canvas
- Superbe collection de démos HTML dans le canevas par la communauté