Signatur
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 mittransferToImageBitmap()oderconvertToBlob().
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
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);
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>wedergetContext()aufrufen nochwidth/heightsetzen, sonst wird eineInvalidStateErrorgeworfen. Größenänderungen muss man daher per Nachricht an den Worker schicken. - Transfer-Liste nicht vergessen: Ohne das zweite Argument
[offscreen]beipostMessage()wird einDataCloneErrorgeworfen. - Nur ein Kontext: Wie bei
<canvas>kann einOffscreenCanvasnur einen Kontexttyp haben. Ein zweitergetContext()-Aufruf mit einem anderen Typ gibtnullzurück. - Keine DOM-Hilfsmittel im Worker:
Image,documentund CSS-Schriften der Seite gibt es dort nicht. Bilder lädt man mitfetch()undcreateImageBitmap(), Schriften überself.fontsundFontFace. - Unterschiede zu
HTMLCanvasElement: StatttoDataURL()undtoBlob()gibt es nur das Promise-basierteconvertToBlob(). Dieses wirft eineSecurityError, wenn das Canvas durch Bilder anderer Herkunft ohne CORS „verunreinigt“ (tainted) ist. - Kompatibilität: Firefox unterstützt
OffscreenCanvasvollstä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 selfbzw.'transferControlToOffscreen' in HTMLCanvasElement.prototype. Für ältere Browser braucht man eine Ausweichlösung im Hauptthread. - Speicher: Nicht mehr benötigte
ImageBitmaps sollte man mitclose()freigeben, besonders in Animationsschleifen.