Per anni, gli sviluppatori web hanno dovuto fare una scelta architettonica difficile quando hanno creato applicazioni visive complesse e altamente interattive sul web: si affidano al DOM per le sue ricche funzionalità semantiche o eseguono il rendering direttamente nell'elemento <canvas> per prestazioni grafiche di basso livello?
Con la nuova API HTML-in-Canvas sperimentale, ora disponibile nella prova dell'origine, non devi scegliere. Questa API ti consente di disegnare contenuti DOM direttamente in un canvas 2D o in una texture WebGL/WebGPU mantenendo l'interattività, l'accessibilità e la connessione dell'UI alle tue funzionalità del browser preferite. Combinando HTML con l'elaborazione grafica di basso livello, puoi creare esperienze che prima erano impossibili.
DOM e Canvas
Per comprendere la potenza di questa nuova API, è utile esaminare i punti di forza relativi sia del DOM sia del Canvas.
Il DOM è l'elemento base dell'UI web. Offre soluzioni di layout di testo pronte all'uso, utilizzando contenuti semanticamente compresi per creare interfacce avanzate. In questo modo, gli utenti possono eseguire senza problemi operazioni comuni nelle pagine web, cose che spesso diamo per scontate, come evidenziare il testo da copiare o fare clic con il tasto destro del mouse su un'immagine per salvarla. Il DOM si integra anche con le funzionalità essenziali del browser: strumenti di accessibilità, traduzione, ricerca nella pagina, modalità lettura, estensioni, modalità scura, zoom del browser e compilazione automatica.
Canvas (e WebGL/WebGPU), d'altra parte, consente l'accesso di basso livello per gestire una griglia di pixel per la grafica 2D e 3D altamente avanzata. I giochi e le app web complesse (come Google Documenti o Figma) richiedono questo accesso di basso livello e ad alte prestazioni. Poiché il canvas è fondamentalmente una griglia di pixel, il supporto di funzionalità come il testo reattivo richiedeva una logica UI personalizzata complessa, aumentando drasticamente le dimensioni del bundle. È fondamentale che tutte le potenti funzionalità del browser integrate nel DOM si interrompano completamente quando l'UI è intrappolata all'interno di una griglia di pixel canvas statica.
I vantaggi di portare il DOM in Canvas
L'API HTML-in-Canvas è il ponte che ti offre il meglio di entrambi i mondi. Inserendo HTML all'interno dell'elemento <canvas> e sincronizzando la relativa trasformazione, ti assicuri che i contenuti rimangano completamente interattivi e che tutte le integrazioni del browser funzionino automaticamente.
Ecco cosa ottieni consentendo al DOM di gestire l'UI all'interno di un elemento <canvas>:
- Layout e formattazione del testo: layout e formattazione del testo semplificati, inclusi testo multiriga o bidirezionale con stili CSS applicati.
- Controlli dei moduli: controlli dei moduli espressivi e più facili da usare con ampie opzioni di personalizzazione.
- Selezione del testo, copia/incolla e clic con il tasto destro del mouse: gli utenti possono evidenziare il testo all'interno delle scene 3D o fare clic con il tasto destro del mouse sui menu contestuali in modo nativo.
- Selezione del testo, copia/incolla e clic con il tasto destro del mouse: gli utenti possono evidenziare il testo all'interno delle scene 3D o fare clic con il tasto destro del mouse sui menu contestuali in modo nativo.
- Accessibilità: i contenuti di cui è stato eseguito il rendering all'interno del canvas vengono esposti all'albero di accessibilità. I sistemi di accessibilità possono analizzare l'UI come fanno con il normale HTML ed esporla a sistemi come gli screen reader.
- Find-in-page: gli utenti possono utilizzare la ricerca nella pagina (Ctrl/Cmd+F) per cercare il testo e il browser lo evidenzierà direttamente all'interno delle texture WebGL.
- Find-in-page: gli utenti possono utilizzare la ricerca nella pagina (Ctrl/Cmd+F) per cercare il testo e il browser lo evidenzierà direttamente all'interno delle texture WebGL.
- Indicizzazione e interfaccia con agenti AI: i crawler web e gli agenti AI possono indicizzare e leggere senza problemi il testo di cui è stato eseguito il rendering nelle scene 2D e 3D.
- Integrazione delle estensioni: le estensioni del browser funzionano in modo nativo. Ad esempio, un'estensione di sostituzione del testo aggiornerà automaticamente il testo di cui è stato eseguito il rendering sulle mesh 3D.
- Integrazione di DevTools: puoi ispezionare i contenuti del canvas, inclusi gli elementi dell'UI WebGL/WebGPU direttamente in Chrome DevTools. Modifica uno stile CSS nell'inspector e guarda l'aggiornamento immediato sulla texture 3D.
Casi d'uso generali
Questa API sblocca un potenziale incredibile in diversi domini:
- Applicazioni di grandi dimensioni basate su canvas: le app web complesse come Google Documenti, Miro o Figma ora possono eseguire il rendering dei componenti dell'UI dell'applicazione complessa in modo nativo nei relativi spazi di lavoro basati su canvas, migliorando l'accessibilità e riducendo il peso del bundle.
- Scene e giochi 3D: i siti di marketing, le esperienze WebXR immersive e i giochi web ora possono inserire un'UI web completamente interattiva nelle scene 3D, ad esempio un libro 3D che utilizza testo DOM reale o un terminale di gioco che supporta in modo nativo la copia e l'incolla.
Come utilizzare l'API
L'utilizzo dell'API avviene in tre fasi: configurazione del canvas, rendering nel canvas e aggiornamento della trasformazione CSS in modo che il browser sappia dove si trova fisicamente l'elemento sullo schermo.
Prerequisiti
L'API HTML-in-Canvas è in prova dell'origine in Chrome 148-150. Per testarla sul tuo sito, utilizza Chrome Canary 149 o versioni successive con il flag chrome://flags/#canvas-draw-element attivato. Per attivare l'API per altri utenti, registrati a prova dell'origine.
Passaggio 1: configurazione di base del canvas
Innanzitutto, aggiungi l'attributo layoutsubtree al tag <canvas>. In questo modo, il browser riconosce i contenuti nidificati all'interno del canvas, preparandoli per la visualizzazione all'interno del canvas ed esponendoli agli alberi di 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>
Ridimensiona la griglia del canvas
Per evitare che i contenuti di cui è stato eseguito il rendering siano sfocati, assicurati di ridimensionare la griglia del canvas in modo che corrisponda al fattore di scala del dispositivo.
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);
Passaggio 2: rendering
Per un contesto 2D, utilizza il metodo drawElementImage. Esegui questa operazione all'interno dell'evento paint, che viene attivato ogni volta che l'elemento viene ridisegnato, ad esempio durante l'evidenziazione del testo o l'input dell'utente. È fondamentale aggiornare la trasformazione CSS dell'elemento con il valore restituito in modo che l'interattività continui a funzionare.
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...
};
Esegui il rendering con WebGL
Per WebGL, utilizza texElementImage2D. Funziona in modo simile a texImage2D, ma utilizza l'elemento DOM come origine.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Esegui il rendering con WebGPU
WebGPU utilizza il metodo copyElementImageToTexture nella coda del dispositivo, in modo analogo a copyExternalImageToTexture:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Passaggio 3: aggiorna la trasformazione CSS
Ora che hai eseguito il rendering dell'elemento nel canvas, dovrai aggiornare il browser sulla sua posizione. In questo modo, si garantisce la sincronizzazione spaziale tra il canvas e il layout del DOM. È importante che il browser possa mappare correttamente la zona dell'evento, ad esempio il punto esatto in cui l'utente fa clic o passa il mouse sopra, con il punto in cui viene eseguito il rendering dell'elemento.
Per il caso del contesto 2D, applica la trasformazione restituita dalla chiamata di rendering a .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();
};
Con WebGL o WebGPU, la posizione sullo schermo di un elemento dipende da come la texture di output viene utilizzata dal codice shader e non può essere dedotta dal contesto di rendering del canvas. Tuttavia, se il programma shader utilizza una proiezione di visualizzazione del modello tipica per disegnare la texture, puoi utilizzare la nuova funzione di praticità element.getElementTransform() per calcolare una trasformazione che può essere utilizzata nello stesso modo del valore restituito da drawElementImage(). Per facilitare questa operazione, devi:
- Convertire la matrice WebGL MVP in matrice DOM.
- Normalizzare l'elemento HTML. Le dimensioni degli elementi HTML sono espresse in pixel (ad esempio, 200 px di larghezza). WebGL, tuttavia, di solito tratta gli oggetti come "quadrati unitari", ad esempio da 0 a 1. Se non esegui la normalizzazione, il pulsante di 200 px apparirà 200 volte più grande.
- Mappare all'area visibile del canvas. Questo passaggio è la fase di "ridimensionamento": estende di nuovo la matematica dello spazio unitario in modo che corrisponda alle dimensioni effettive in pixel dell'elemento
<canvas>sullo schermo. Inoltre, capovolge l'asse Y, perché in WebGL, verso l'alto è positivo, ma in CSS, verso il basso è positivo. - Calcolare la trasformazione finale. Moltiplica le matrici nel seguente ordine:
Viewport * MVP * Normalization.La combinazione in una trasformazione finale produce una "mappa" che indica al browser la posizione esatta in cui deve trovarsi il livello dell'elemento HTML per allinearsi al disegno 3D. - Applica la trasformazione all'elemento HTML. In questo modo, il livello dell'elemento HTML si trova direttamente sopra i pixel di cui è stato eseguito il rendering. In questo modo, quando un utente fa clic su un pulsante o seleziona il testo, colpisce l'elemento HTML reale.
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();
}
}
Supporto di librerie e framework
Alcune delle librerie più diffuse hanno già implementato il supporto per la funzionalità HTML-in-Canvas.
Three.js
L'aggiornamento manuale delle matrici può essere noioso, motivo per cui i framework stanno già aderendo. Three.js ha un supporto sperimentale che utilizza il nuovo 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 supporta anche HTML-in-Canvas utilizzando la relativa API 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();
Demo
Prima di provare le demo, assicurati che l'ambiente sia configurato correttamente.
Esistono diverse demo che fungono da riferimento per l'utilizzo dell'API. Stiamo già vedendo soluzioni creative dalla community, che vanno dai libri 3D traducibili agli elementi dell'UI che si rifrangono attraverso gli shader di vetro:
- Il libro 3D: un libro 3D di cui è stato eseguito il rendering WebGL che utilizza il layout HTML per le pagine. Gli utenti possono cambiare i caratteri con CSS. Poiché è basato su DOM, la traduzione integrata funziona immediatamente e gli agenti AI possono estrarre il testo con meno complessità.
- UI 3D interattive: un cursore a gelatina WebGPU che rifrange la luce in base a un modello 3D sottostante, pur rispondendo agli attributi di passaggio HTML
<input type="range">standard. - Texture animate: un cartellone pubblicitario 3D dinamico che esegue il rendering di una matita SVG animata utilizzando il DOM direttamente in una texture WebGL senza bisogno di un loop di animazione personalizzato.
- Overlay rifrattivi: un livello di tipografia interattivo distorto da un cursore 3D in movimento, ma completamente selezionabile e ricercabile utilizzando la ricerca in-page.
Dai un'occhiata alla raccolta di demo create dalla community. Se vuoi che la tua demo HTML-in-Canvas venga inclusa in questa raccolta, crea una richiesta di pull per aggiungerla.
Limitazioni
Sebbene sia potente, l'API presenta alcune limitazioni consapevoli:
- Contenuti cross-origin: per motivi di sicurezza e privacy, l'API non funziona con i contenuti iframe cross-origin.
- Scorrimento del thread principale: il rendering di HTML-in-Canvas viene eseguito con JavaScript, il che significa che lo scorrimento e le animazioni non possono essere aggiornati indipendentemente da JavaScript, come possono fare al di fuori del canvas. Gli sviluppatori devono valutare attentamente le caratteristiche di rendimento dell'inserimento di contenuti scorrevoli all'interno del canvas rispetto allo scorrimento dell'intero canvas.
Feedback
Se stai sperimentando l'API HTML-in-Canvas, vogliamo conoscere la tua opinione. Puoi registrarti alla prova dell'origine per attivare la funzionalità sul tuo sito durante la fase sperimentale per aiutarci a definire la progettazione dell'API. Puoi anche presentare un problema per fornire feedback.
Risorse
- Supporto di HTML-in-Canvas in Three.js
- Demo di HTML-in-Canvas in Three.js
- Supporto di HTML-in-Canvas in PlayCanvas: documentazione per gli sviluppatori
- Demo di HTML-in-Canvas in PlayCanvas
- HTML-in-Canvas: spiegazione
- Guida al web moderno per gli strumenti di codifica AI per HTML-in-Canvas
- Demo di Chrome.dev per HTML-in-Canvas
- Raccolta di demo di HTML-in-Canvas della community