Per anni, gli sviluppatori web hanno dovuto fare una scelta architetturale difficile quando creavano applicazioni visive complesse e altamente interattive sul web: affidarsi al DOM per le sue ricche funzionalità semantiche o eseguire il rendering direttamente nell'elemento <canvas> per prestazioni grafiche di basso livello?
Con la nuova API HTML in Canvas sperimentale, disponibile ora 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à e l'accessibilità dell'interfaccia utente e collegandola alle tue funzionalità del browser preferite. Combinando l'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 che di Canvas.
Il DOM è l'elemento principale dell'interfaccia utente web. Offre soluzioni di layout del testo pronte all'uso, utilizzando contenuti compresi semanticamente per creare interfacce avanzate. In questo modo, gli utenti possono eseguire facilmente operazioni comuni nelle pagine web, 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, trova nella pagina, modalità di lettura, estensioni, modalità Buio, 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 una grafica 2D e 3D molto avanzata. I giochi e le app web complesse (come Documenti Google o Figma) richiedono questo accesso a basso livello e ad alte prestazioni. Poiché il canvas è fondamentalmente una griglia di pixel, il supporto di funzionalità come il testo reattivo richiedeva una complessa logica UI personalizzata, aumentando drasticamente le dimensioni del bundle. Fondamentalmente, tutte le potenti funzionalità del browser integrate nel DOM si interrompono completamente quando la UI è intrappolata all'interno di una griglia di pixel statica del canvas.
Vantaggi dell'importazione del DOM in Canvas
L'API HTML in Canvas è il ponte che ti offre il meglio di entrambi i mondi. Se inserisci l'HTML all'interno dell'elemento <canvas> e sincronizzi la relativa trasformazione, ti assicuri che i contenuti rimangano completamente interattivi e che tutte le integrazioni del browser funzionino automaticamente.
Ecco cosa ottieni se lasci che il DOM gestisca la tua UI all'interno di un elemento <canvas>:
- Layout e formattazione del testo:layout e formattazione del testo semplificati, inclusi testo multilinea o bidirezionale con stili CSS applicati.
- Controlli modulo:controlli modulo 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 visualizzati all'interno del canvas sono esposti all'albero di accessibilità. I sistemi di accessibilità possono analizzare la UI come fanno con l'HTML normale ed esporla a sistemi come gli screen reader.
- Find-in-page::gli utenti possono utilizzare la funzione Trova nella pagina (Ctrl/Cmd+F) per cercare testo e il browser lo evidenzierà direttamente all'interno delle texture WebGL.
- Find-in-page::gli utenti possono utilizzare la funzione Trova nella pagina (Ctrl/Cmd+F) per cercare testo e il browser lo evidenzierà direttamente all'interno delle texture WebGL.
- Indicizzazione e interfacciabilità con gli agenti AI:i web crawler e gli agenti AI possono indicizzare e leggere senza problemi il testo visualizzato 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 visualizzato nelle mesh 3D.
- Integrazione di DevTools:puoi ispezionare i contenuti del canvas, inclusi gli elementi dell'interfaccia utente 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 basate su canvas di grandi dimensioni:le app web pesanti come Google Docs, Miro o Figma ora possono eseguire il rendering dei componenti complessi dell'interfaccia utente dell'applicazione in modo nativo nei propri 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'interfaccia utente web completamente interattiva in scene 3D, ad esempio un libro 3D che utilizza testo DOM reale o un terminale in-game che supporta nativamente 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 fase di prova dell'origine in Chrome 148-150. Per testarlo 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 alla prova dell'origine.
Passaggio 1: configurazione di base del canvas
Innanzitutto, aggiungi l'attributo layoutsubtree al tag <canvas>. In questo modo, il browser è a conoscenza dei 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>
Ridimensionare la griglia del canvas
Per evitare la sfocatura dei contenuti sottoposti a rendering, assicurati di dimensionare 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...
};
Rendering con WebGL
Per WebGL, utilizzi texElementImage2D. Funziona in modo simile a texImage2D, ma prende 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);
}
};
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, devi aggiornare il browser sulla sua posizione. In questo modo si garantisce la sincronizzazione spaziale tra il canvas e il layout del DOM. Questo è importante per consentire al browser di 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 dal modo in cui 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 della visualizzazione del modello tipica per disegnare la texture, puoi utilizzare la nuova funzione di convenienza element.getElementTransform() per calcolare una trasformazione che può essere utilizzata nello stesso modo del valore restituito da drawElementImage(). Per semplificare questa procedura, devi:
- Converti la matrice MVP WebGL in matrice DOM.
- Normalizza 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, che vanno da 0 a 1. Se non esegui la normalizzazione, il pulsante da 200 px apparirà 200 volte più grande.
- Mappa all'area visibile del canvas. Questo passaggio è la fase di "ridimensionamento": estende nuovamente la matematica dello spazio unitario per corrispondere alle dimensioni effettive in pixel dell'elemento
<canvas>sullo schermo. Inoltre, inverte l'asse Y, perché in WebGL la direzione verso l'alto è positiva, mentre in CSS la direzione verso il basso è positiva. - Calcola la trasformazione finale. Moltiplica le matrici in ordine:
Viewport * MVP * Normalization.La combinazione in un'unica 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 sottoposti a rendering. In questo modo, quando un utente fa clic su un pulsante o seleziona un testo, tocca 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ù popolari supportano già la funzionalità HTML in Canvas.
Three.js
L'aggiornamento manuale delle matrici può essere noioso, motivo per cui i framework sono già in fase di implementazione. Three.js offre il supporto sperimentale utilizzando 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 da parte della community, che vanno dai libri 3D traducibili agli elementi dell'interfaccia utente che si rifrangono attraverso gli shader di vetro:
- Il libro 3D: un libro 3D di cui è stato eseguito il rendering con WebGL che utilizza il layout HTML per le sue pagine. Gli utenti possono scambiare i caratteri con CSS. Poiché è basata sul DOM, la traduzione integrata funziona immediatamente e gli agenti AI possono estrarre il testo con meno complessità.
- UI 3D interattive: un cursore gelatinoso 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 rendering dinamico di un cartellone 3D che mostra una matita SVG animata utilizzando il DOM direttamente in una texture WebGL senza bisogno di un ciclo di animazione personalizzato.
- Sovrapposizioni rifrattive: un livello di tipografia interattiva distorto da un cursore 3D in movimento, ma completamente selezionabile e ricercabile utilizzando la funzionalità Trova nella pagina.
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 multiorigine: per motivi di sicurezza e privacy, l'API non funziona con i contenuti iframe multiorigine.
- Scorrimento del thread principale:l'HTML in canvas viene disegnato con JavaScript, il che significa che lo scorrimento e le animazioni non possono essere aggiornati indipendentemente da JavaScript, come invece avviene 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 e aiutarci a definire la progettazione dell'API. Puoi anche segnalare un problema per fornire un feedback.
Risorse
- Supporto di HTML in Canvas in Three.js
- HTML in Canvas nella demo di Three.js
- Supporto di HTML in Canvas in PlayCanvas: documentazione per gli sviluppatori
- HTML in Canvas nella demo di PlayCanvas
- HTML in Canvas: spiegazione
- Indicazioni per il web moderno per gli strumenti di codifica AI per HTML in Canvas
- Demo di Chrome.dev per HTML in Canvas
- Fantastica raccolta di demo HTML in Canvas della community