OffscreenCanvas & Canvas API Advanced
Move canvas rendering to Web Workers with OffscreenCanvas, and master advanced Canvas 2D API techniques for animations and effects.
Overview
Key Points
- •`canvas.transferControlToOffscreen()` transfers canvas rendering to a Worker
- •`worker.postMessage({ canvas: offscreen }, [offscreen])` pass as transferable to worker
- •In the worker: `const ctx = offscreenCanvas.getContext("2d")` works identically to regular canvas
- •`ctx.commit()` in a worker immediately updates the displayed frame
- •Canvas state: `ctx.save()` and `ctx.restore()` push/pop transforms, styles, and clipping paths
- •`ctx.requestAnimationFrame` not available in workers use `setTimeout` or `MessageChannel` for animation loops
- •`createImageBitmap(blob/image)` async, worker-safe image loading for canvas
Examples
Transferring canvas to an OffscreenCanvas Worker
<!-- main.html -->
<canvas id="game-canvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('game-canvas');
// Transfer canvas control to a worker
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('/game-worker.js');
worker.postMessage({ type: 'INIT', canvas: offscreen }, [offscreen]);
// [offscreen] in the transferable list transferred, not copied
// Send game state updates to worker
document.addEventListener('keydown', (e) => {
worker.postMessage({ type: 'KEYDOWN', key: e.key });
});
worker.onmessage = (e) => {
if (e.data.type === 'SCORE_UPDATE') {
document.getElementById('score').textContent = e.data.score;
}
};
</script>After transferControlToOffscreen(), the main thread can no longer draw to the canvas.
OffscreenCanvas Worker game loop
// game-worker.js
let ctx, width, height, animating = false;
let playerX = 400, gameScore = 0;
self.onmessage = (e) => {
switch (e.data.type) {
case 'INIT':
ctx = e.data.canvas.getContext('2d');
width = e.data.canvas.width;
height = e.data.canvas.height;
startGameLoop();
break;
case 'KEYDOWN':
if (e.data.key === 'ArrowLeft') playerX -= 10;
if (e.data.key === 'ArrowRight') playerX += 10;
break;
}
};
function startGameLoop() {
animating = true;
let lastTime = 0;
function frame(time) {
if (!animating) return;
const dt = time - lastTime;
lastTime = time;
// Clear
ctx.clearRect(0, 0, width, height);
// Draw background
ctx.fillStyle = '#1e293b';
ctx.fillRect(0, 0, width, height);
// Draw player
ctx.fillStyle = '#3b82f6';
ctx.beginPath();
ctx.arc(playerX, 500, 20, 0, Math.PI * 2);
ctx.fill();
// Update score
gameScore += dt * 0.01;
self.postMessage({ type: 'SCORE_UPDATE', score: Math.floor(gameScore) });
// Schedule next frame (no requestAnimationFrame in workers)
setTimeout(() => frame(performance.now()), 16);
}
frame(performance.now());
}Workers cannot use requestAnimationFrame use setTimeout(fn, 16) for approximately 60fps.
Advanced Canvas 2D: gradients, compositing, and clipping
<canvas id="demo" width="400" height="400"></canvas>
<script>
const canvas = document.getElementById('demo');
const ctx = canvas.getContext('2d');
// 1. Linear gradient
const gradient = ctx.createLinearGradient(0, 0, 400, 0);
gradient.addColorStop(0, '#3b82f6');
gradient.addColorStop(0.5, '#8b5cf6');
gradient.addColorStop(1, '#ec4899');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 400, 400);
// 2. Clipping path only draw inside this shape
ctx.save();
ctx.beginPath();
ctx.arc(200, 200, 150, 0, Math.PI * 2);
ctx.clip();
// 3. Compositing globalCompositeOperation
ctx.globalCompositeOperation = 'screen';
ctx.fillStyle = 'rgba(255, 200, 0, 0.8)';
ctx.fillRect(50, 50, 300, 300);
ctx.restore(); // remove clipping path
// 4. Radial gradient
const radial = ctx.createRadialGradient(200, 200, 0, 200, 200, 100);
radial.addColorStop(0, 'rgba(255,255,255,0.8)');
radial.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = radial;
ctx.beginPath();
ctx.arc(200, 200, 100, 0, Math.PI * 2);
ctx.fill();
</script>ctx.save/restore stack the canvas state transforms, clips, and style settings enabling safe isolated drawing operations.
Common Mistakes
- Calling canvas.transferControlToOffscreen() and then trying to draw to the original canvas the main thread loses all access after transfer
- Using requestAnimationFrame inside a Web Worker it's not available; use setTimeout or MessageChannel with port.onmessage
- Forgetting ctx.save()/ctx.restore() around clipping paths clips accumulate and affect later drawing operations
Note
For image processing, use `createImageBitmap(file)` to decode images in workers this avoids blocking the main thread for large image decoding. `ImageBitmap` objects are transferable between workers and the main thread.
Exercises
Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4