Start · Sprachen · JavaScript · Referenz · OffscreenCanvas

OffscreenCanvas

Schnittstelle

Eine Zeichenfläche ohne Bindung an das DOM, die auch in Web Workern rendern kann und den Hauptthread so von aufwendigen Canvas-Arbeiten entlastet.

seit JavaScript Baseline 2023: Chrome 69, Edge 79, Firefox 105, Sa Kategorie: webapi

Signatur

interface OffscreenCanvas extends EventTarget

Beschreibung

OffscreenCanvas ist eine Zeichenfläche, die nicht an das DOM gebunden ist. Normalerweise laufen Rendering, Animation und Benutzerinteraktion eines <canvas>-Elements im Hauptthread. Aufwendige Zeichenarbeit kann dort Ruckler und träge Eingaben verursachen. Mit OffscreenCanvas lässt sich das Rendern in einen Web Worker verlagern, und der Hauptthread bleibt frei für Layout und Benutzereingaben.

Es gibt zwei typische Einsatzarten:

  • Übertragenes Canvas: Mit HTMLCanvasElement.transferControlToOffscreen() gibt man die Kontrolle über ein sichtbares <canvas> an einen Worker ab. Alles, was der Worker zeichnet, erscheint automatisch auf der Seite.
  • Eigenständiges Canvas: Mit new OffscreenCanvas(width, height) erzeugt man eine unsichtbare Zeichenfläche, etwa für Bildverarbeitung, Thumbnails oder Texturen. Das Ergebnis erhält man mit transferToImageBitmap() oder convertToBlob().

Wichtige Mitglieder sind width und height, getContext() (unterstützt "2d", "webgl", "webgl2", "bitmaprenderer" und "webgpu"), transferToImageBitmap(), convertToBlob() sowie die Ereignisse contextlost und contextrestored. OffscreenCanvas ist übertragbar (transferable). Man kann es also per postMessage() ohne Kopie an einen Worker übergeben.

Parameter

Name Typ Default Beschreibung
$width Pflicht number Breite der Zeichenfläche in Pixeln (Konstruktor-Argument). Wird auf eine ganze Zahl ohne Vorzeichen umgewandelt.
$height Pflicht number Höhe der Zeichenfläche in Pixeln (Konstruktor-Argument). Wird auf eine ganze Zahl ohne Vorzeichen umgewandelt.

Rückgabewert

Typ
OffscreenCanvas
Beschreibung
new OffscreenCanvas(width, height) gibt eine neue, leere Zeichenfläche ohne Rendering-Kontext zurück. Einen Kontext erhält man über getContext().

Beispiele

Rendering an einen Worker übergeben (Hauptthread)

// main.js
const canvas = document.querySelector('#scene');
const offscreen = canvas.transferControlToOffscreen();

const worker = new Worker(new URL('./render-worker.js', import.meta.url), { type: 'module' });
// Zweites Argument: Transfer-Liste – das Canvas wird übertragen, nicht kopiert
worker.postMessage({ canvas: offscreen, dpr: devicePixelRatio }, [offscreen]);

// Größenänderungen weiterreichen – das Canvas-Element selbst ist jetzt gesperrt
new ResizeObserver(([entry]) => {
  const { width, height } = entry.contentRect;
  worker.postMessage({ resize: { width, height } });
}).observe(canvas);

Animationsschleife im Worker

// render-worker.js
let ctx, dpr = 1;

self.onmessage = ({ data }) => {
  if (data.canvas) {
    dpr = data.dpr;
    ctx = data.canvas.getContext('2d');
    requestAnimationFrame(draw);
  }
  if (data.resize && ctx) {
    ctx.canvas.width = data.resize.width * dpr;
    ctx.canvas.height = data.resize.height * dpr;
  }
};

const draw = (t) => {
  const { width, height } = ctx.canvas;
  ctx.clearRect(0, 0, width, height);
  ctx.fillStyle = '#0a7';
  const x = (Math.sin(t / 500) * 0.4 + 0.5) * width;
  ctx.beginPath();
  ctx.arc(x, height / 2, 20 * dpr, 0, Math.PI * 2);
  ctx.fill();
  requestAnimationFrame(draw); // in Dedicated Workern verfügbar
};

Vorschaubild als Blob erzeugen (ohne DOM)

const createThumbnail = async (file, maxSize = 200) => {
  const bitmap = await createImageBitmap(file);
  const scale = Math.min(maxSize / bitmap.width, maxSize / bitmap.height, 1);
  const canvas = new OffscreenCanvas(
    Math.round(bitmap.width * scale),
    Math.round(bitmap.height * scale)
  );
  const ctx = canvas.getContext('2d');
  ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  bitmap.close(); // Speicher freigeben
  return canvas.convertToBlob({ type: 'image/webp', quality: 0.8 });
};

const blob = await createThumbnail(someFile);
console.log(blob.type, blob.size > 0);
image/webp true

Frame als ImageBitmap an ein sichtbares Canvas übergeben

const off = new OffscreenCanvas(256, 256);
const octx = off.getContext('2d');
ocxt = null; // (Platzhalter entfernt)
octx.fillStyle = 'tomato';
octx.fillRect(0, 0, 256, 256);

const bitmap = off.transferToImageBitmap();
const target = document.querySelector('canvas');
target.getContext('bitmaprenderer').transferFromImageBitmap(bitmap);
console.log(bitmap.width); // Bitmap wurde übertragen und ist jetzt geleert

// Wichtig · Fallstricke

  • Einmalige Übertragung: Nach transferControlToOffscreen() kann man auf dem ursprünglichen <canvas> weder getContext() aufrufen noch width/height setzen, sonst wird eine InvalidStateError geworfen. Größenänderungen muss man daher per Nachricht an den Worker schicken.
  • Transfer-Liste nicht vergessen: Ohne das zweite Argument [offscreen] bei postMessage() wird ein DataCloneError geworfen.
  • Nur ein Kontext: Wie bei <canvas> kann ein OffscreenCanvas nur einen Kontexttyp haben. Ein zweiter getContext()-Aufruf mit einem anderen Typ gibt null zurück.
  • Keine DOM-Hilfsmittel im Worker: Image, document und CSS-Schriften der Seite gibt es dort nicht. Bilder lädt man mit fetch() und createImageBitmap(), Schriften über self.fonts und FontFace.
  • Unterschiede zu HTMLCanvasElement: Statt toDataURL() und toBlob() gibt es nur das Promise-basierte convertToBlob(). Dieses wirft eine SecurityError, wenn das Canvas durch Bilder anderer Herkunft ohne CORS „verunreinigt“ (tainted) ist.
  • Kompatibilität: Firefox unterstützt OffscreenCanvas vollständig erst ab Version 105, Safari den 2D-Kontext ab 16.4 und WebGL ab 17. Prüfen lässt sich die Unterstützung mit 'OffscreenCanvas' in self bzw. 'transferControlToOffscreen' in HTMLCanvasElement.prototype. Für ältere Browser braucht man eine Ausweichlösung im Hauptthread.
  • Speicher: Nicht mehr benötigte ImageBitmaps sollte man mit close() freigeben, besonders in Animationsschleifen.