Jahrelang mussten Webentwickler bei der Entwicklung komplexer, hochinteraktiver visueller Anwendungen im Web eine schwierige architektonische Entscheidung treffen: Sollten sie das DOM für seine umfangreichen semantischen Funktionen nutzen oder direkt auf das <canvas>-Element rendern, um eine leistungsstarke Grafik auf niedriger Ebene zu erzielen?
Mit der neuen experimentellen HTML-in-Canvas API, die jetzt im Ursprungstest verfügbar ist, müssen Sie sich nicht entscheiden. Mit dieser API können Sie DOM-Inhalte direkt in ein 2D-Canvas oder eine WebGL-/WebGPU-Textur zeichnen und gleichzeitig dafür sorgen, dass die Benutzeroberfläche interaktiv und zugänglich bleibt und mit Ihren bevorzugten Browserfunktionen verknüpft ist. Durch die Kombination von HTML mit der Verarbeitung von Grafiken auf niedriger Ebene können Sie Lösungen entwickeln, die bisher nicht möglich waren.
DOM im Vergleich zu Canvas
Um das Potenzial dieser neuen API zu verstehen, ist es hilfreich, sich die relativen Stärken von DOM und Canvas anzusehen.
Das DOM ist das Fundament der Web-UI. Es bietet sofort einsatzbereite Lösungen für das Textlayout, bei denen semantisch verstandene Inhalte verwendet werden, um ansprechende Benutzeroberflächen zu erstellen. So können Nutzer gängige Vorgänge auf Webseiten nahtlos ausführen, die wir oft als selbstverständlich ansehen, z. B. Text zum Kopieren markieren oder mit der rechten Maustaste auf ein Bild klicken, um es zu speichern. Das DOM ist auch in wichtige Browserfunktionen integriert: Bedienungshilfen, Übersetzen, Auf der Seite suchen, Lesemodus, Erweiterungen, dunkler Modus, Browserzoom und Autofill.
Canvas (und WebGL/WebGPU) hingegen ermöglicht den Zugriff auf niedriger Ebene, um ein Raster von Pixeln für hochentwickelte 2D- und 3D-Grafiken zu steuern. Spiele und komplexe Web-Apps wie Google Docs oder Figma benötigen diesen leistungsstarken Zugriff auf niedriger Ebene. Da der Canvas im Grunde ein Raster aus Pixeln ist, waren für die Unterstützung von Funktionen wie responsivem Text komplexe benutzerdefinierte UI-Logik erforderlich, was die Bundle-Größe drastisch erhöht hat. Alle leistungsstarken Browserfunktionen, die in das DOM integriert sind, funktionieren nicht mehr, wenn die Benutzeroberfläche in einem statischen Canvas-Pixelraster eingeschlossen ist.
Vorteile der DOM-Integration in Canvas
Die HTML-in-Canvas API ist die Brücke, die Ihnen das Beste aus beiden Welten bietet. Wenn Sie HTML-Code in das <canvas>-Element einfügen und die Transformation synchronisieren, bleibt der Inhalt vollständig interaktiv und alle Browserintegrationen funktionieren automatisch.
Wenn Sie das DOM Ihre Benutzeroberfläche in einem <canvas>-Element verarbeiten lassen, erhalten Sie Folgendes:
- Textlayout und ‑formatierung: Vereinfachtes Textlayout und ‑formatierung, einschließlich mehrzeiligen oder bidirektionalen Texts mit angewendeten CSS-Stilen.
- Formularsteuerelemente: Ausdrucksstarke und benutzerfreundliche Formularsteuerelemente mit umfangreichen Anpassungsoptionen.
- Textauswahl, Kopieren/Einfügen und Rechtsklick: Nutzer können Text in Ihren 3D-Szenen markieren oder Kontextmenüs per Rechtsklick aufrufen.
- Barrierefreiheit: Inhalte, die im Canvas gerendert werden, sind im Barrierefreiheitsbaum verfügbar. Bedienungshilfen können die Benutzeroberfläche wie normales HTML parsen und für Systeme wie Screenreader verfügbar machen.
- In-Page-Suche: Nutzer können mit der In-Page-Suche (Strg/Cmd + F) nach Text suchen. Der Browser hebt ihn dann direkt in Ihren WebGL-Texturen hervor.
- Indexierbarkeit und Kompatibilität mit KI-Agenten: Webcrawler und KI-Agenten können den in Ihren 2D- und 3D-Szenen gerenderten Text nahtlos indexieren und lesen.
- Erweiterungsintegration: Browsererweiterungen funktionieren nativ. Mit einer Erweiterung zum Ersetzen von Text wird beispielsweise der Text, der auf Ihren 3D-Modellen gerendert wird, automatisch aktualisiert.
- Einbindung der Entwicklertools: Sie können Ihre Canvas-Inhalte, einschließlich WebGL-/WebGPU-UI-Elemente, direkt in den Chrome-Entwicklertools untersuchen. Sie können einen CSS-Stil im Inspector anpassen und sehen, wie er sofort auf die 3D-Textur angewendet wird.
Allgemeine Anwendungsfälle
Diese API bietet unglaubliches Potenzial in verschiedenen Bereichen:
- Anwendungen mit großem Canvas: Schwergewichtige Web-Apps können jetzt komplexe UI-Komponenten nativ in ihren Canvas-basierten Arbeitsbereichen rendern. Das verbessert die Barrierefreiheit und reduziert das Bundle-Gewicht.
- 3D-Szenen und ‑Spiele: Auf Marketingwebsites, in immersiven WebXR-Erlebnissen und in Webspielen kann jetzt eine vollständig interaktive Web-Benutzeroberfläche in 3D-Szenen platziert werden, z. B. ein 3D-Buch, in dem echter DOM-Text verwendet wird, oder ein In-Game-Terminal, das das Kopieren und Einfügen nativ unterstützt.
Verwendung der API
Die Verwendung der API erfolgt in drei Phasen: Einrichten des Canvas, Rendern in den Canvas und Aktualisieren der CSS-Transformation, damit der Browser weiß, wo sich das Element physisch auf dem Bildschirm befindet.
Vorbereitung
Die HTML-in-Canvas API befindet sich in Chrome 148 bis 150 in einem Ursprungstest. Wenn Sie es auf Ihrer Website testen möchten, verwenden Sie Chrome Canary 149 oder höher mit aktiviertem chrome://flags/#canvas-draw-element-Flag. Wenn Sie die API für andere Nutzer aktivieren möchten, registrieren Sie sich für den Ursprungstest.
Schritt 1: Canvas-Grundeinrichtung
Fügen Sie zuerst das Attribut layoutsubtree zu Ihrem <canvas>-Tag hinzu. So wird der Browser auf die Inhalte aufmerksam, die im Canvas eingebettet sind. Er bereitet sie für die Anzeige im Canvas vor und macht sie für Barrierefreiheitsbäume verfügbar.
<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>
Canvas-Raster anpassen
Damit der gerenderte Inhalt nicht verschwommen ist, muss das Canvas-Raster an den Skalierungsfaktor des Geräts angepasst werden.
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);
Schritt 2: Rendern
Verwenden Sie für einen 2D-Kontext die Methode drawElementImage. Führen Sie dies innerhalb des paint-Ereignisses aus, das immer dann ausgelöst wird, wenn das Element neu gezeichnet wird, z. B. beim Markieren von Text oder bei der Nutzereingabe. Es ist wichtig, die CSS-Transformation des Elements mit dem Rückgabewert zu aktualisieren, damit die Interaktivität weiterhin funktioniert.
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...
};
Mit WebGL rendern
Für WebGL verwenden Sie texElementImage2D. Sie funktioniert ähnlich wie texImage2D, verwendet aber das DOM-Element als Quelle.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Mit WebGPU rendern
WebGPU verwendet die Methode copyElementImageToTexture in der Geräte-Queue, analog zu copyExternalImageToTexture:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Schritt 3: CSS-Transformation aktualisieren
Nachdem Sie das Element auf dem Canvas gerendert haben, müssen Sie den Browser darüber informieren, wo es sich befindet. So wird die räumliche Synchronisierung zwischen dem Canvas und dem Layout des DOM sichergestellt. Das ist wichtig, damit der Browser die Ereigniszone (z. B. die Stelle, an der der Nutzer klickt oder den Mauszeiger platziert) korrekt dem gerenderten Element zuordnen kann.
Wenden Sie im Fall des 2D-Kontexts die von dem Rendering-Aufruf zurückgegebene Transformation auf .style.transform property an:
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();
};
Bei WebGL oder WebGPU hängt die Position eines Elements auf dem Bildschirm davon ab, wie die Ausgabetextur vom Shader-Code verwendet wird. Sie kann nicht aus dem Canvas-Renderingkontext abgeleitet werden. Wenn Ihr Shader-Programm jedoch eine typische Modell-Ansichts-Projektion zum Zeichnen der Textur verwendet, können Sie mit der neuen Convenience-Funktion element.getElementTransform() eine Transformation berechnen, die auf dieselbe Weise wie der Rückgabewert von drawElementImage() verwendet werden kann. Dazu müssen Sie Folgendes tun:
- WebGL-MVP-Matrix in DOM-Matrix konvertieren:
- HTML-Element normalisieren: HTML-Elemente werden in Pixeln angegeben (z. B. 200 Pixel breit). In WebGL werden Objekte jedoch in der Regel als „Einheitsquadrate“ behandelt, z. B. im Bereich von 0 bis 1. Wenn Sie nicht normalisieren, wird Ihr 200‑Pixel-Button 200‑mal größer dargestellt.
- Dem Canvas-Darstellungsbereich zuordnen: Dieser Schritt ist die Phase der „Neuskalierung“. Dabei wird die Berechnung des Einheitenbereichs so angepasst, dass sie den tatsächlichen Pixelabmessungen des
<canvas>-Elements auf dem Bildschirm entspricht. Außerdem wird die Y-Achse gespiegelt, da in WebGL „nach oben“ positiv ist, in CSS aber „nach unten“. - Berechnen Sie die endgültige Transformation. Multiplizieren Sie die Matrizen in der richtigen Reihenfolge:
Viewport * MVP * Normalization.Wenn Sie sie in einer endgültigen Transformation kombinieren, erhalten Sie eine „Karte“, die dem Browser genau sagt, wo die HTML-Elementebene platziert werden soll, damit sie mit der 3D-Zeichnung übereinstimmt. - Wenden Sie die Transformation auf das HTML-Element an. Dadurch wird die HTML-Elementebene direkt über den gerenderten Pixeln platziert. So wird sichergestellt, dass der Nutzer beim Klicken auf eine Schaltfläche oder beim Auswählen von Text das tatsächliche HTML-Element trifft.
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();
}
}
Unterstützung von Bibliotheken und Frameworks
Einige der beliebtesten Bibliotheken unterstützen die Funktion „HTML in Canvas“ bereits.
Three.js
Das manuelle Aktualisieren von Matrizen kann mühsam sein. Deshalb werden Frameworks bereits integriert. Three.js bietet experimentelle Unterstützung mit dem neuen 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 unterstützt auch HTML-in-Canvas über die Texture 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();
Demos
Bevor Sie die Demos ausprobieren, müssen Sie sicherstellen, dass Ihre Umgebung richtig konfiguriert ist.
Es gibt mehrere Demos, die als Referenz für die Verwendung der API dienen. Wir sehen bereits kreative Lösungen aus der Community, von übersetzbaren 3D-Büchern bis hin zu UI-Elementen, die durch Glasshader gebrochen werden:
- Das 3D-Buch: Ein mit WebGL gerendertes 3D-Buch, dessen Seiten mit HTML-Layout erstellt wurden. Nutzer können Schriftarten mit CSS tauschen. Da die integrierte Übersetzung DOM-basiert ist, funktioniert sie sofort und KI-Agents können den Text mit weniger Komplexität extrahieren.
- Interaktive 3D-Benutzeroberflächen: Ein WebGPU-Jelly-Slider, der Licht basierend auf einem zugrunde liegenden 3D-Modell bricht und gleichzeitig auf Standard-HTML-
<input type="range">-Schrittattribute reagiert. - Animierte Texturen: Ein dynamisches 3D-Billboard, das einen animierten SVG-Stift direkt in eine WebGL-Textur rendert, ohne dass eine benutzerdefinierte Animationsschleife erforderlich ist.
- Brechende Overlays: Eine interaktive Typografieschicht, die durch einen sich bewegenden 3D-Cursor verzerrt wird, aber dennoch vollständig auswählbar und mit der Funktion „Auf Seite suchen“ durchsuchbar ist.
Hier finden Sie eine Sammlung von Demos, die von der Community erstellt wurden. Wenn Sie möchten, dass Ihre HTML-in-Canvas-Demo in dieser Sammlung enthalten ist, erstellen Sie eine Pull-Anfrage, um sie hinzuzufügen.
Beschränkungen
Die API ist zwar leistungsstark, hat aber einige bewusste Einschränkungen:
- Ursprungsübergreifende Inhalte: Aus Sicherheits- und Datenschutzgründen funktioniert die API nicht mit ursprungsübergreifenden iFrame-Inhalten.
- Scrollen im Hauptthread: HTML-in-Canvas wird mit JavaScript gerendert. Das bedeutet, dass Scrolling und Animationen nicht unabhängig von JavaScript aktualisiert werden können, wie es außerhalb von Canvas möglich ist. Entwickler sollten sorgfältig abwägen, ob sie scrollbare Inhalte in ein Canvas-Element einfügen oder das gesamte Canvas-Element scrollen lassen.
Feedback
Wenn Sie die HTML-in-Canvas-API testen, würden wir uns über Ihr Feedback freuen. Sie können sich für den Origin-Testlauf registrieren, um die Funktion auf Ihrer Website zu aktivieren, während sie sich in der Testphase befindet. So können Sie uns helfen, das API-Design zu verbessern. Sie können auch ein Problem melden, um Feedback zu geben.
Ressourcen
- Unterstützung von HTML in Canvas in Three.js
- HTML-in-Canvas-Demo in Three.js
- HTML-in-Canvas-Unterstützung in PlayCanvas: Entwicklerdokumentation
- HTML-in-Canvas-Demo in PlayCanvas
- HTML-in-Canvas: Erläuterung
- Modern Web Guidance for AI coding tools for HTML-in-Canvas
- Chrome.dev-Demos für HTML-in-Canvas
- Tolle Sammlung von HTML-in-Canvas-Demos von der Community