OffscreenCanvas: velocizza le operazioni del canvas con un worker web

Tim Dresser

Canvas è un modo molto utilizzato per disegnare ogni tipo di grafica sullo schermo e un punto di accesso al mondo di WebGL. Può essere utilizzato per disegnare forme, immagini, eseguire animazioni o persino visualizzare ed elaborare contenuti video. Viene spesso utilizzato per creare esperienze utente straordinarie in applicazioni web e giochi online con contenuti multimediali.

È possibile scrivere script, il che significa che i contenuti disegnati su Canvas possono essere creati in modo programmatico, ad esempio in JavaScript. Ciò conferisce a Canvas una grande flessibilità.

Allo stesso tempo, nei siti web moderni, l'esecuzione di script è una delle più frequenti cause di problemi di reattività degli utenti. Poiché la logica e il rendering della tela avvengono nello stesso thread dell'interazione utente, gli calcoli (a volte pesanti) coinvolti nelle animazioni possono danneggiare il rendimento reale e percepito dell'app.

Fortunatamente, OffscreenCanvas è una risposta a questa minaccia.

Supporto dei browser

  • Chrome: 69.
  • Edge: 79.
  • Firefox: 105.
  • Safari: 16.4.

Origine

In precedenza, le funzionalità di disegno della tela erano legate all'elemento <canvas>, il che significa che dipendevano direttamente dal DOM. Come suggerisce il nome, OffscreenCanvas consente di disaccoppiare il DOM e l'API Canvas spostandoli fuori dallo schermo.

Grazie a questo disaccoppiamento, il rendering di OffscreenCanvas è completamente scollegato dal DOM e quindi offre alcuni miglioramenti della velocità rispetto al canvas normale, in quanto non esiste alcuna sincronizzazione tra i due.

Inoltre, può essere utilizzato in un worker web, anche se non è disponibile un DOM. Ciò consente tutti i tipi di casi d'uso interessanti.

Utilizzare OffscreenCanvas in un worker

I worker sono la versione web dei thread: ti consentono di eseguire attività in background.

Spostare parte dello scripting in un worker offre alla tua app più spazio per eseguire attività critiche per l'utente sul thread principale. Senza OffscreenCanvas, non era possibile utilizzare l'API Canvas in un worker, poiché non era disponibile alcun DOM.

OffscreenCanvas non dipende dal DOM, quindi può essere utilizzato. L'esempio seguente utilizza OffscreenCanvas per calcolare un colore a gradiente in un worker:

// file: worker.js
function getGradientColor(percent) {
 
const canvas = new OffscreenCanvas(100, 1);
 
const ctx = canvas.getContext('2d');
 
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
  gradient
.addColorStop(0, 'red');
  gradient
.addColorStop(1, 'blue');
  ctx
.fillStyle = gradient;
  ctx
.fillRect(0, 0, ctx.canvas.width, 1);
 
const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
 
const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
 
return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}

getGradientColor
(40);  // rgba(152, 0, 104, 255 )

Sbloccare il thread principale

Spostare i calcoli complessi in un worker ti consente di liberare risorse significative nel thread principale. Utilizza il metodo transferControlToOffscreen per eseguire il mirroring della normale canvas in un'istanza OffscreenCanvas. Le operazioni applicate a OffscreenCanvas verranno visualizzate automaticamente sulla tela di origine.

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker
.postMessage({canvas: offscreen}, [offscreen]);

Nel seguente esempio, il calcolo complesso avviene quando cambia il tema a colori. Dovrebbe impiegare alcuni millisecondi anche su un computer veloce. Puoi scegliere di eseguire le animazioni nel thread principale o nel worker. Nel caso del thread principale, non puoi interagire con il pulsante mentre l'attività pesante è in esecuzione, poiché il thread è bloccato. Nel caso del worker, non ha alcun impatto sulla reattività dell'interfaccia utente.

Demo

L'operazione funziona anche al contrario: il thread principale occupato non influisce sull'animazione in esecuzione su un worker. Puoi utilizzare questa funzionalità per evitare scatti visivi e garantire un'animazione fluida nonostante il traffico del thread principale, come mostrato nella seguente demo.

Demo

Nel caso di una normale canvas, l'animazione si interrompe quando il thread principale viene sovraccaricato artificialmente, mentre OffscreenCanvas basato su worker viene riprodotto senza problemi.

Poiché l'API OffscreenCanvas è generalmente compatibile con l'elemento Canvas normale, puoi usarla come miglioramento progressivo, anche con alcune delle principali librerie grafiche sul mercato.

Ad esempio, puoi rilevarlo e, se disponibile, utilizzarlo con Three.js specificando l'opzione canvas nel costruttore del renderer:

const canvasEl = document.querySelector('canvas');
const canvas =
 
'OffscreenCanvas' in window
   
? canvasEl.transferControlToOffscreen()
   
: canvasEl;
canvas
.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});

L'unico problema è che Three.js si aspetta che il canvas abbia una proprietà style.width e style.height. OffscreenCanvas, essendo completamente scollegato dal DOM, non lo ha, quindi devi fornirlo autonomamente, o tramite stub o fornendo una logica che colleghi questi valori alle dimensioni del canvas originale.

Di seguito viene mostrato come eseguire un'animazione Three.js di base in un worker:

Demo

Tieni presente che alcune API relative al DOM non sono immediatamente disponibili in un worker, quindi se vuoi utilizzare funzionalità più avanzate di Three.js come le texture, potresti aver bisogno di altri workaround. Per alcune idee su come iniziare a fare esperimenti, guarda il video di Google I/O 2017.

Se utilizzi molto le funzionalità grafiche di Canvas, OffscreenCanvas può influire positivamente sul rendimento della tua app. La disponibilità dei contesti di rendering della tela per i worker aumenta il parallelismo nelle applicazioni web e consente di utilizzare meglio i sistemi multi-core.

Risorse aggiuntive