OffscreenCanvas – Canvas-Vorgänge mit einem Web Worker beschleunigen

Tim Dresser

Canvas ist eine beliebte Methode, um alle Arten von Grafiken auf dem Bildschirm zu zeichnen. Außerdem ist es ein Einstiegspunkt in die Welt von WebGL. Sie können damit Formen, Bilder zeichnen, Animationen ausführen oder sogar Videoinhalte anzeigen und verarbeiten. Es wird häufig verwendet, um eine ansprechende Nutzererfahrung in medienreichen Webanwendungen und Onlinespielen zu schaffen.

Es ist scriptfähig, d. h., die auf dem Canvas gezeichneten Inhalte können programmatisch erstellt werden, z. B. in JavaScript. Das bietet Canvas eine große Flexibilität.

Gleichzeitig ist die Scriptausführung auf modernen Websites eine der häufigsten Ursachen für Probleme mit der Nutzerreaktionszeit. Da die Canvas-Logik und das Rendering im selben Thread wie die Nutzerinteraktion erfolgen, können die (manchmal umfangreichen) Berechnungen, die für Animationen erforderlich sind, die tatsächliche und wahrgenommene Leistung der App beeinträchtigen.

Glücklicherweise ist OffscreenCanvas eine Antwort auf diese Bedrohung.

Unterstützte Browser

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

Quelle

Bisher waren die Canvas-Zeichnenfunktionen an das <canvas>-Element gebunden, was bedeutete, dass sie direkt vom DOM abhängen. Wie der Name schon sagt, trennt OffscreenCanvas das DOM von der Canvas API, indem es sie außerhalb des Bildschirms platziert.

Dank dieser Entkopplung ist das Rendering von OffscreenCanvas vollständig vom DOM getrennt und bietet daher einige Geschwindigkeitsverbesserungen gegenüber dem regulären Canvas, da es keine Synchronisierung zwischen den beiden gibt.

Außerdem kann es in einem Webworker verwendet werden, auch wenn kein DOM verfügbar ist. Das ermöglicht alle möglichen interessanten Anwendungsfälle.

OffscreenCanvas in einem Worker verwenden

Worker sind die Webversion von Threads. Mit ihnen können Sie Aufgaben im Hintergrund ausführen.

Wenn Sie einen Teil Ihres Scripts in einen Worker verschieben, hat Ihre App mehr Spielraum, um nutzerkritische Aufgaben im Hauptthread auszuführen. Ohne OffscreenCanvas gab es keine Möglichkeit, die Canvas API in einem Worker zu verwenden, da kein DOM verfügbar war.

OffscreenCanvas ist vom DOM unabhängig und kann daher verwendet werden. Im folgenden Beispiel wird OffscreenCanvas verwendet, um einen Farbverlauf in einem Worker zu berechnen:

// 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 )

Blockierung des Hauptthreads aufheben

Wenn Sie rechenintensive Berechnungen auf einen Worker verschieben, können Sie erhebliche Ressourcen im Haupt-Thread freisetzen. Verwenden Sie die Methode transferControlToOffscreen, um das reguläre Canvas in einer OffscreenCanvas-Instanz zu spiegeln. Vorgänge, die auf OffscreenCanvas angewendet werden, werden automatisch auf dem Quell-Canvas gerendert.

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

Im folgenden Beispiel wird die aufwendige Berechnung ausgeführt, wenn sich das Farbschema ändert. Das sollte selbst auf einem schnellen Computer nur wenige Millisekunden dauern. Sie können festlegen, ob Animationen im Haupt- oder im Worker-Thread ausgeführt werden sollen. Im Fall des Hauptthreads können Sie nicht mit der Schaltfläche interagieren, während die ressourcenintensive Aufgabe ausgeführt wird, da der Thread blockiert ist. Im Fall des Workers hat dies keine Auswirkungen auf die UI-Reaktionsfähigkeit.

Demo

Das funktioniert auch umgekehrt: Der beschäftigte Hauptthread hat keinen Einfluss auf die Animation, die auf einem Worker ausgeführt wird. Mit dieser Funktion können Sie visuelle Ruckler vermeiden und trotz Traffic im Hauptthread eine flüssige Animation gewährleisten, wie in der folgenden Demo gezeigt.

Demo

Bei einem regulären Canvas wird die Animation angehalten, wenn der Hauptthread künstlich überlastet wird, während der workerbasierte OffscreenCanvas flüssig abgespielt wird.

Da die OffscreenCanvas API im Allgemeinen mit dem regulären Canvas-Element kompatibel ist, können Sie sie als progressive Verbesserung verwenden, auch mit einigen der führenden Grafikbibliotheken auf dem Markt.

Sie können es beispielsweise per Feature-Erkennung erkennen und bei Verfügbarkeit mit Three.js verwenden, indem Sie die Canvas-Option im Renderer-Konstruktor angeben:

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});

Das Problem dabei ist, dass Three.js von Canvas die Eigenschaften style.width und style.height erwartet. Da OffscreenCanvas vollständig vom DOM getrennt ist, gibt es keine. Sie müssen sie also selbst angeben, entweder durch Auslagern oder durch Bereitstellung einer Logik, die diese Werte mit den ursprünglichen Canvas-Abmessungen verknüpft.

Im Folgenden wird gezeigt, wie eine einfache Three.js-Animation in einem Worker ausgeführt wird:

Demo

Einige der DOM-bezogenen APIs sind in einem Worker nicht direkt verfügbar. Wenn Sie also erweiterte Three.js-Funktionen wie Texturen verwenden möchten, sind möglicherweise weitere Umgehungslösungen erforderlich. Ideen dazu, wie Sie damit experimentieren können, finden Sie im Video von der Google I/O 2017.

Wenn Sie die grafischen Funktionen von Canvas häufig verwenden, kann OffscreenCanvas die Leistung Ihrer App positiv beeinflussen. Wenn Canvas-Rendering-Kontexte für Worker verfügbar gemacht werden, wird der Parallelismus in Webanwendungen erhöht und Multi-Core-Systeme werden besser genutzt.

Zusätzliche Ressourcen