Pendant des années, les développeurs Web ont dû faire un choix architectural difficile lorsqu'ils créaient des applications visuelles complexes et hautement interactives sur le Web. Faut-il s'appuyer sur le DOM pour ses fonctionnalités sémantiques riches ou effectuer l'affichage directement dans l'élément <canvas> pour des performances graphiques de bas niveau ?
Avec la nouvelle API HTML-in-Canvas expérimentale, désormais disponible en phase d'évaluation, vous n'avez pas à 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é, l'accessibilité et la connexion de l'interface utilisateur à vos fonctionnalités de navigateur préférées. En combinant le HTML avec le traitement graphique de bas niveau, vous pouvez créer des expériences qui étaient auparavant impossibles.
DOM et Canvas
Pour comprendre la puissance de cette nouvelle API, il est utile d'examiner les points forts relatifs du DOM et du canevas.
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 sémantiquement compris pour créer des interfaces enrichies. Cela permet aux utilisateurs d'effectuer des opérations courantes sur les pages Web de manière fluide, des choses que nous tenons souvent pour acquises, comme surligner du texte pour le 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 lecteur, extensions, mode sombre, zoom du navigateur et saisie automatique.
Canvas (et WebGL/WebGPU), en revanche, permet un accès de bas niveau pour piloter une grille de pixels pour des graphismes 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 auparavant une logique d'UI personnalisée complexe, ce qui augmentait considérablement la taille de votre bundle. Surtout, toutes les fonctionnalités puissantes du navigateur intégrées au DOM ne fonctionnent plus du tout 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 la passerelle qui vous permet de profiter du meilleur des deux mondes. En plaçant du 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 de 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 simplifiées du texte, y compris du texte multiligne ou bidirectionnel avec des styles CSS appliqués.
- Éléments de contrôle des formulaires : éléments de contrôle des formulaires expressifs 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 effectuer un clic droit sur les menus contextuels 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.
- Recherche sur la page : les utilisateurs peuvent utiliser la recherche sur la page (Ctrl/Cmd+F) pour rechercher du texte, et le navigateur le met en surbrillance directement dans vos textures WebGL.
- Indexabilité et interface avec les agents IA : les robots d'exploration et les agents 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 met 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 observez sa mise à jour instantanée sur la texture 3D.
Cas d'utilisation généraux
Cette API offre un potentiel incroyable dans plusieurs domaines :
- Applications volumineuses basées sur un canevas : les applications Web lourdes peuvent désormais afficher des composants d'UI d'application complexes de manière native dans leurs espaces de travail basés sur un 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 une UI Web entièrement interactive dans des scènes 3D, comme un livre 3D qui utilise du texte DOM réel ou un terminal de jeu qui accepte nativement le copier-coller.
Utiliser l'API
L'utilisation de l'API se déroule en trois phases : configuration du canevas, rendu dans le canevas et mise à jour de la transformation CSS pour que le navigateur sache où l'élément se trouve physiquement sur l'écran.
Prérequis
L'API HTML-in-Canvas est en phase d'essai Origin Trial dans Chrome 148 à 150. Pour la tester sur votre site, utilisez Chrome Canary 149 ou une version ultérieure avec le flag chrome://flags/#canvas-draw-element activé. Pour activer l'API pour d'autres utilisateurs, inscrivez-vous à l'essai Origin Trial.
Étape 1 : Configuration de base de Canvas
Tout d'abord, ajoutez l'attribut layoutsubtree à votre balise <canvas>. Le navigateur est ainsi informé du contenu imbriqué dans le canevas, ce qui lui permet de le préparer pour l'affichage dans le canevas et de 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 affiché ne soit flou, veillez à dimensionner la grille du canevas en fonction du facteur de scaling 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. Pour ce faire, utilisez 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 la saisie de l'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 utilise 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 manière analogue à 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 de l'événement (par exemple, l'endroit exact où l'utilisateur clique ou pointe) avec l'endroit où l'élément est affiché.
Pour le cas du contexte 2D, appliquez la transformation renvoyée par l'appel de rendu au .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 et 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 standard pour dessiner la texture, vous pouvez utiliser la nouvelle fonction pratique element.getElementTransform() pour calculer une transformation pouvant être utilisée de la même manière que la valeur de retour de drawElementImage(). Pour ce faire, vous devez procéder comme suit :
- Convertir 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). Cependant, WebGL traite généralement les objets comme des "carrés unitaires", par exemple, de 0 à 1. Si vous ne normalisez pas, votre bouton de 200 px sera 200 fois plus grand.
- Mappez à la fenêtre d'affichage du canevas. Cette étape est la phase de "redimensionnement". Il étire ensuite ces 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. - Calculer 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 l'emplacement exact de la couche de l'élément HTML pour qu'elle s'aligne sur le dessin 3D. - Appliquez la transformation à l'élément HTML. Cela déplace la couche de l'élément HTML pour qu'elle se trouve directement au-dessus de ses pixels affichés. Cela garantit que lorsqu'un utilisateur clique sur un bouton ou sélectionne du texte, il interagit avec l'élément HTML réel.
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 des bibliothèques les plus populaires sont déjà compatibles avec la fonctionnalité HTML-in-Canvas.
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 de manière 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 voyons déjà des solutions créatives de la communauté, allant des livres 3D traduisibles aux éléments d'interface utilisateur qui se réfractent à travers des nuanceurs de verre :
- Livre 3D : livre 3D affiché avec WebGL qui utilise une mise en page HTML pour ses pages. Les utilisateurs peuvent changer de police avec CSS. Comme elle est basée sur le DOM, la traduction intégrée fonctionne instantanément, et les agents IA peuvent extraire le texte plus facilement.
- UI 3D interactives : un curseur en gelée WebGPU qui réfracte la lumière en fonction d'un modèle 3D sous-jacent, tout en répondant aux attributs d'étape HTML
<input type="range">standards. - Textures animées : un panneau d'affichage 3D dynamique affichant un crayon SVG animé à l'aide du DOM directement dans une texture WebGL, sans nécessiter de boucle d'animation personnalisée.
- Superpositions réfractives : calque de typographie interactif déformé par un curseur 3D en mouvement, mais entièrement sélectionnable et interrogeable à l'aide de la fonctionnalité de recherche sur la page.
Découvrez la collection de démonstrations créées par la communauté. Si vous souhaitez que votre démo HTML-in-Canvas soit présentée dans cette collection, créez une demande d'extraction pour l'ajouter.
Limites
Bien que puissante, l'API présente quelques limites :
- Contenu interorigine : pour des raisons de sécurité et de confidentialité, l'API ne fonctionne pas avec le contenu d'un iFrame interorigine.
- 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 ils le peuvent en dehors du canevas. Les développeurs doivent examiner attentivement les caractéristiques de performances de l'insertion de contenu à faire défiler dans un canevas par rapport au défilement de l'intégralité du canevas.
Commentaires
Si vous testez l'API HTML-in-Canvas, nous aimerions avoir votre avis. Vous pouvez vous inscrire à l'essai Origin Trial pour activer la fonctionnalité sur votre site pendant sa phase expérimentale et nous aider à concevoir l'API. Vous pouvez également signaler un problème pour nous faire part de vos commentaires.
Ressources
- Compatibilité HTML-in-Canvas dans Three.js
- Démo HTML-in-Canvas dans Three.js
- Compatibilité HTML-in-Canvas dans PlayCanvas : documentation pour les développeurs
- Démo : HTML-in-Canvas dans PlayCanvas
- HTML-in-Canvas: Explainer
- Modern Web Guidance pour les outils de codage IA pour HTML-in-Canvas
- Démos Chrome.dev pour HTML-in-Canvas
- Superbe collection de démos HTML-in-Canvas par la communauté