Webontwikkelaars moesten jarenlang een moeilijke architectonische keuze maken als ze complexe, zeer interactieve visuele apps op het web maakten. Moet je het DOM gebruiken vanwege de uitgebreide semantische functies of rechtstreeks renderen naar het element <canvas> voor grafische prestaties op laag niveau?
Met de nieuwe experimentele HTML-in-Canvas API, die nu beschikbaar is in de origin trial, hoef je niet te kiezen. Met deze API kun je DOM-content rechtstreeks in een 2D-canvas of een WebGL-/WebGPU-textuur tekenen, terwijl de UI interactief en toegankelijk blijft en gekoppeld blijft aan je favoriete browserfuncties. Door HTML te combineren met grafische verwerking op laag niveau, kun je ervaringen creëren die eerder onmogelijk waren.
DOM versus Canvas
Als je de kracht van deze nieuwe API wilt begrijpen, is het handig om de relatieve sterke punten van zowel de DOM als het canvas te bekijken.
Het DOM is het belangrijkste onderdeel van de web-UI. De tool biedt kant-en-klare oplossingen voor tekstlay-out en gebruikt semantisch begrepen content om rijke interfaces te maken. Zo kunnen gebruikers naadloos veelvoorkomende bewerkingen uitvoeren op webpagina's, zoals tekst markeren om te kopiëren of met de rechtermuisknop op een afbeelding klikken om deze op te slaan. Het DOM is ook geïntegreerd met essentiële browserfuncties: toegankelijkheidstools, vertalen, zoeken op pagina, leesmodus, extensies, donkere modus, browserzoom en automatisch invullen.
Canvas (en WebGL/WebGPU) biedt daarentegen toegang op laag niveau om een raster van pixels aan te sturen voor zeer geavanceerde 2D- en 3D-afbeeldingen. Games en complexe web-apps (zoals Google Documenten of Figma) hebben deze krachtige, low-level toegang nodig. Omdat het canvas in wezen een raster van pixels is, waren voor ondersteunende functies zoals responsieve tekst complexe aangepaste UI-logica nodig, waardoor de bundelgrootte drastisch toenam. Cruciaal is dat alle krachtige browserfuncties die in de DOM zijn geïntegreerd, volledig worden verbroken als de UI vastzit in een statisch canvaspixelraster.
De voordelen van het overbrengen van het DOM naar Canvas
De HTML-in-Canvas API is de brug die je het beste van beide werelden biedt. Door HTML in het element <canvas> te plaatsen en de transformatie ervan te synchroniseren, zorg je dat de content volledig interactief blijft en dat alle browserintegraties automatisch werken.
Dit krijg je als je het DOM je UI laat afhandelen in een <canvas>-element:
- Tekstindeling en -opmaak: Vereenvoudigde tekstindeling en -opmaak, waaronder tekst met meerdere regels of bidirectionele tekst waarop CSS-stijlen zijn toegepast.
- Formulierfuncties: Expressieve en gebruiksvriendelijkere formulierfuncties met uitgebreide aanpassingsopties.
- Tekstselectie, kopiëren/plakken en klikken met de rechtermuisknop: Gebruikers kunnen tekst in je 3D-scènes markeren of klikken met de rechtermuisknop om contextmenu's te openen.
- Toegankelijkheid: Content die in het canvas wordt weergegeven, wordt blootgesteld aan de toegankelijkheidsstructuur. Toegankelijkheidssystemen kunnen de UI parseren zoals ze dat doen met normale HTML en deze beschikbaar maken voor systemen zoals schermlezers.
- Zoeken op pagina: Gebruikers kunnen Zoeken op pagina (Ctrl/Cmd+F) gebruiken om naar tekst te zoeken. De browser markeert de tekst rechtstreeks in je WebGL-texturen.
- Indexeerbaarheid en AI-agentinterface: Webcrawlers en AI-agents kunnen de tekst die in je 2D- en 3D-scènes wordt weergegeven, naadloos indexeren en lezen.
- Extensie-integratie: Browserextensies werken native. Een extensie voor tekstvervanging updatet bijvoorbeeld automatisch de tekst die op uw 3D-meshes wordt weergegeven.
- DevTools-integratie: Je kunt je canvascontent, waaronder voor WebGL/WebGPU UI-elementen, rechtstreeks inspecteren in Chrome DevTools. Pas een CSS-stijl aan in de inspecteur en zie deze meteen updaten in de 3D-textuur.
Toepassingen op hoog niveau
Deze API biedt ongelofelijke mogelijkheden voor verschillende domeinen:
- Grote canvasgebaseerde apps: Zware web-apps kunnen nu complexe UI-componenten van apps native renderen in hun canvasgestuurde werkruimten. Dit verbetert de toegankelijkheid en vermindert het bundelgewicht.
- 3D-scènes en -games: Marketingsites, immersieve WebXR-functionaliteit en webgames kunnen nu volledig interactieve web-UI in 3D-scènes plaatsen, zoals een 3D-boek dat echte DOM-tekst gebruikt of een in-game terminal die kopiëren en plakken native ondersteunt.
De API gebruiken
Het gebruik van de API gebeurt in 3 fasen: je canvas instellen, renderen in het canvas en de CSS-transformatie updaten zodat de browser weet waar het element zich fysiek op het scherm bevindt.
Vereisten
De HTML-in-Canvas API bevindt zich in de origin trial in Chrome 148 tot en met 150. Als je dit op je site wilt testen, gebruik je Chrome Canary 149 of hoger met de flag chrome://flags/#canvas-draw-element aangezet. Als je de API wilt aanzetten voor andere gebruikers, registreer je je voor de Origin Trial.
Stap 1: Basisinstellingen voor Canvas
Voeg eerst het kenmerk layoutsubtree toe aan de tag <canvas>. Hierdoor is de browser op de hoogte van de content die is genest in het canvas, wordt deze voorbereid op weergave in het canvas en wordt deze blootgesteld aan toegankelijkheidsbomen.
<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>
Het canvasraster aanpassen
Zorg dat het canvasraster overeenkomt met de schaalfactor van het apparaat om te voorkomen dat de gerenderde content wazig wordt.
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);
Stap 2: Weergave
Gebruik voor een 2D-context de methode drawElementImage. Doe dit in de gebeurtenis paint, die wordt geactiveerd wanneer het element opnieuw wordt getekend, bijvoorbeeld tijdens het markeren van tekst of gebruikersinvoer. Het is essentieel dat u de CSS-transformatie van het element updatet met de retourwaarde, zodat de interactiviteit blijft werken.
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...
};
Renderen met WebGL
Voor WebGL gebruik je texElementImage2D. Deze functie werkt vergelijkbaar met texImage2D, maar gebruikt het DOM-element als bron.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Renderen met WebGPU
WebGPU gebruikt de methode copyElementImageToTexture in de apparaatwachtrij, vergelijkbaar met copyExternalImageToTexture:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Stap 3: Update de CSS-transformatie
Nu je het element op het canvas hebt gerenderd, moet je de browser updaten over de locatie. Dit zorgt voor ruimtelijke synchronisatie tussen het canvas en de indeling van het DOM. Dit is belangrijk zodat de browser de gebeurteniszone (zoals waar de gebruiker precies klikt of de muisaanwijzer plaatst) correct kan toewijzen aan de plek waar het element wordt weergegeven.
Pas voor de 2D-context de transformatie die door de weergaveaanroep wordt geretourneerd toe op de .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();
};
Met WebGL of WebGPU is de locatie van een element op het scherm afhankelijk van hoe de uitvoertextuur wordt gebruikt door shadercode en kan deze niet worden afgeleid uit de canvasweergavecontext. Als je shaderprogramma een typische modelweergaveprojectie gebruikt om de textuur te tekenen, kun je de nieuwe handige functie element.getElementTransform() gebruiken om een transformatie te berekenen die op dezelfde manier kan worden gebruikt als de retourwaarde van drawElementImage(). Doe hiervoor het volgende:
- Zet de WebGL MVP-matrix om naar de DOM-matrix.
- Normaliseer het HTML-element. HTML-elementen worden gemeten in pixels (bijvoorbeeld 200 pixels breed). WebGL behandelt objecten meestal als eenheidsvierkanten, bijvoorbeeld van 0 tot 1. Als je niet normaliseert, ziet je knop van 200 px er 200 keer groter uit.
- Toewijzen aan het canvas-kijkvenster. Deze stap is de fase van herschaling. De wiskundige berekening van de eenheidsruimte wordt weer uitgerekt zodat deze overeenkomt met de werkelijke pixelafmetingen van je
<canvas>-element op het scherm. De functie draait ook de Y-as om, omdat in WebGL omhoog positief is, maar in CSS omlaag positief is. - Bereken de uiteindelijke transformatie. Vermenigvuldig de matrices in de juiste volgorde:
Viewport * MVP * Normalization.Als je ze combineert tot één uiteindelijke transformatie, krijg je een 'kaart' die de browser precies vertelt waar die HTML-elementlaag moet worden geplaatst om uit te lijnen met de 3D-tekening. - Pas de transformatie toe op het HTML-element. Hiermee wordt de HTML-elementlaag rechtstreeks boven de gerenderde pixels geplaatst. Zo zorgt u ervoor dat wanneer een gebruiker op een knop klikt of tekst selecteert, deze het echte HTML-element raakt.
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();
}
}
Ondersteuning voor bibliotheken en frameworks
Sommige populaire bibliotheken bieden al ondersteuning voor de functie HTML-in-Canvas.
Three.js
Het handmatig updaten van matrices kan vervelend zijn. Daarom springen frameworks al aan boord. Three.js heeft experimentele ondersteuning met de nieuwe 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 ondersteunt ook HTML-in-Canvas via de textuur-API:
// 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();
Demo's
Zorg dat je omgeving juist is ingesteld voordat je de demo's uitprobeert.
Er zijn verschillende demo's die als referentie dienen voor het gebruik van de API. We zien al creatieve oplossingen van de community, van vertaalbare 3D-boeken tot UI-elementen die breken door glasshaders:
- Het 3D-boek: Een 3D-boek dat is gerenderd met WebGL en een HTML-indeling gebruikt voor de pagina's. Gebruikers kunnen lettertypen wisselen met CSS. Omdat de ingebouwde vertaling op DOM is gebaseerd, werkt deze onmiddellijk en kunnen AI-agents de tekst met minder complexiteit extraheren.
- Interactieve 3D-UI's: Een WebGPU-jelly-schuifregelaar die licht breekt op basis van een onderliggend 3D-model, terwijl deze nog steeds reageert op standaard HTML-
<input type="range">-stapkenmerken. - Geanimeerde texturen: Een dynamisch 3D-billboard dat een geanimeerd SVG-potlood rendert met de DOM rechtstreeks in een WebGL-textuur zonder dat er een aangepaste animatieloop nodig is.
- Refractieve overlays: Een interactieve typografielaag die wordt vervormd door een bewegende 3D-cursor, maar die volledig selecteerbaar en doorzoekbaar is met Zoeken op pagina.
Ga naar de verzameling demo's die door de community zijn gemaakt. Als je wilt dat je HTML-in-Canvas-demo in deze collectie wordt opgenomen, maak je een pull-verzoek om de demo toe te voegen.
Beperkingen
De API is krachtig, maar heeft een paar bewuste beperkingen:
- Cross-origin content: Om beveiligings- en privacyredenen werkt de API niet met cross-origin iframe-content.
- Scrollen in de hoofdthread: HTML-in-canvas wordt getekend met JavaScript. Dit betekent dat scrollen en animaties niet onafhankelijk van JavaScript kunnen worden geüpdatet, zoals buiten het canvas. Ontwikkelaars moeten zorgvuldig overwegen wat de prestatiekenmerken zijn van scrollende content in het canvas versus het hele canvas laten scrollen.
Feedback
Als je experimenteert met de HTML-in-Canvas API, horen we graag van je. Je kunt je aanmelden voor de origin trial om de functie op je site aan te zetten terwijl deze zich in de experimentele fase bevindt. Zo kun je ons helpen het API-ontwerp vorm te geven. Je kunt ook een probleem melden om feedback te geven.
Bronnen
- HTML-in-Canvas-ondersteuning in Three.js
- HTML-in-Canvas in Three.js-demo
- HTML-in-Canvas-ondersteuning in PlayCanvas: documentatie voor ontwikkelaars
- Demo van HTML-in-Canvas in PlayCanvas
- HTML-in-Canvas: uitleg
- Modern Web Guidance voor AI-coderingstools voor HTML-in-Canvas
- Chrome.dev-demo's voor HTML-in-Canvas
- Geweldige verzameling HTML-in-Canvas-demo's van de community