OffscreenCanvas API in JS for UI Performance
Learn how to move canvas rendering off the main thread using OffscreenCanvas. Improve animation performance and keep your UI responsive during heavy drawing.
The OffscreenCanvas API lets you render 2D and WebGL graphics inside a Web Worker instead of the main thread. This means your animations and drawings never compete with user interactions, DOM updates, or event handlers. The main thread stays responsive while the worker handles all the heavy rendering work.
The key idea is separating the drawing surface from the display surface. The worker renders to an offscreen canvas, and the main thread copies the result to a visible canvas element.
const canvas = document.querySelector("canvas");
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker("renderer.js");
worker.postMessage({ canvas: offscreen }, [offscreen]);The transferControlToOffscreen method detaches the canvas element's rendering context and returns an OffscreenCanvas. The canvas is then transferred to the worker, which takes ownership. The main thread can no longer draw on the original canvas element.
How OffscreenCanvas works
The main thread owns the visible canvas element in the DOM, but the worker owns the drawing surface. The worker renders frames into the offscreen canvas and sends completed images back to the main thread for display.
The main thread transfers control to the worker once. After that, the worker renders frames independently and sends them back. The main thread only handles displaying the result, keeping its workload minimal.
Rendering inside a worker
The worker receives the OffscreenCanvas and uses it exactly like a regular canvas. The 2D context API, fillRect, arc, drawImage, and all drawing methods work the same way.
In the worker file renderer.js:
self.onmessage = (event) => {
const canvas = event.data.canvas;
const ctx = canvas.getContext("2d");
function draw() {
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.font = "24px sans-serif";
ctx.fillText(new Date().toISOString(), 20, 50);
requestAnimationFrame(draw);
}
draw();
};The worker runs its own animation loop with requestAnimationFrame, completely independent of the main thread. Even if the main thread is busy handling a heavy DOM update or a synchronous network call, the worker's animation continues smoothly without dropping frames.
Sending frames back to the main thread
Drawing in a worker is useful on its own for offscreen rendering, but most apps need to display the result. Use transferToImageBitmap to get a snapshot of the canvas and post it back.
Inside the worker file, wrap the snapshot and transfer in a small helper that runs after each frame is drawn:
function sendFrame(canvas) {
const bitmap = canvas.transferToImageBitmap();
self.postMessage({ bitmap }, [bitmap]);
}Back on the main thread, listen for that message and draw the received bitmap onto the visible canvas that the user actually sees on screen:
const displayCanvas = document.querySelector("#display");
const displayCtx = displayCanvas.getContext("2d");
worker.onmessage = (event) => {
const bitmap = event.data.bitmap;
displayCtx.drawImage(bitmap, 0, 0);
bitmap.close();
};The ImageBitmap is transferred with zero copy overhead. After drawing it to the display canvas, call close to release its memory. This pattern sends frames from the worker to the main thread efficiently.
Checking for OffscreenCanvas support
Not all browsers support OffscreenCanvas. Always check before using it and provide a main-thread fallback.
function getCanvasContext(canvas) {
if (typeof OffscreenCanvas !== "undefined") {
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker("renderer.js");
worker.postMessage({ canvas: offscreen }, [offscreen]);
return;
}
const ctx = canvas.getContext("2d");
startMainThreadRender(ctx);
}When OffscreenCanvas is not available, the function falls back to rendering on the main thread with a regular canvas context. The fallback path runs the same drawing code, just on the main thread instead of a worker.
Common mistakes
- Transferring the canvas twice. transferControlToOffscreen works once per canvas element. The second call throws an error.
- Forgetting to close ImageBitmaps. Each bitmap holds GPU memory. Call bitmap.close() after drawing it to the display canvas.
- Expecting DOM APIs in the worker. The worker has no access to document, window, or DOM elements. All rendering must go through the canvas API.
- Running WebGL in a worker without checking support. Not every browser that supports 2D OffscreenCanvas also supports WebGL contexts inside a worker, so check that getContext returns a valid context before rendering and fall back to the main thread if it returns null.
Related articles
- Advanced Web Workers for High Performance JS -- the worker thread model that OffscreenCanvas builds on
- Creating DOM Elements in JavaScript: Complete Guide -- understanding the canvas element on the main thread
- Changing CSS Styles with JavaScript DOM Methods -- styling the canvas container while the worker renders content
Quick reference
| Operation | Code |
|---|---|
| Transfer canvas to worker | canvas.transferControlToOffscreen() |
| Pass to worker | worker.postMessage({ canvas: offscreen }, [offscreen]) |
| Get 2D context in worker | canvas.getContext("2d") |
| Get frame as bitmap | canvas.transferToImageBitmap() |
| Send bitmap to main thread | self.postMessage({ bitmap }, [bitmap]) |
| Display on main thread | ctx.drawImage(bitmap, 0, 0) |
| Release bitmap memory | bitmap.close() |
| Check support | typeof OffscreenCanvas !== "undefined" |
Rune AI
Key Insights
- OffscreenCanvas lets you render 2D and WebGL graphics inside a Web Worker.
- Call transferControlToOffscreen on a regular canvas to get its OffscreenCanvas handle.
- Pass the handle to a worker via postMessage and render there using the same Canvas API.
- Use transferToImageBitmap to send rendered frames back to the main thread efficiently.
- Fall back to the main thread when OffscreenCanvas is not available.
Frequently Asked Questions
Which browsers support OffscreenCanvas?
Can I use OffscreenCanvas with a 2D context and WebGL?
Does OffscreenCanvas help with canvas text rendering?
How do I get the rendered result back to the main thread?
Conclusion
OffscreenCanvas moves canvas rendering off the main thread into a Web Worker. Create an OffscreenCanvas from a regular canvas using transferControlToOffscreen, pass it to a worker, and render there. Use transferToImageBitmap to send frames back to the main thread for display. The main benefit is keeping animations smooth and the UI responsive during heavy drawing.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.