patterntypescriptModerate
Collaborative Whiteboard Requires Operation Buffering During Canvas Re-Render
Viewed 0 times
whiteboardcanvasdraw operationsrequestAnimationFrameop queuecollaborative drawingjank
Problem
A collaborative drawing/whiteboard app processes incoming draw operations directly on every WebSocket message. Simultaneous drawing from multiple users causes visible jank as canvas re-renders interrupt each other.
Solution
Buffer all incoming operations in a queue. Drain the queue in a single requestAnimationFrame callback, applying all operations and redrawing the canvas once per frame.
type DrawOp = { type: 'stroke'; points: [number, number][]; color: string; width: number };
const opQueue: DrawOp[] = [];
let renderScheduled = false;
function scheduleRender() {
if (!renderScheduled) {
renderScheduled = true;
requestAnimationFrame(() => {
renderScheduled = false;
const ops = opQueue.splice(0);
for (const op of ops) {
applyOpToCanvas(ctx, op);
}
});
}
}
ws.onmessage = (event) => {
opQueue.push(JSON.parse(event.data) as DrawOp);
scheduleRender();
};Why
Canvas 2D drawing is synchronous and compositing happens after JavaScript yields to the browser. Calling drawPath() hundreds of times before the browser paints wastes CPU — batching into rAF ensures one paint cycle per frame.
Gotchas
- Preserve operation order within a frame — splice() removes all queued ops atomically.
- For undo/redo, store operations in a separate history stack, not on the canvas pixel buffer.
- Canvas state (strokeStyle, lineWidth) is mutable global state — save/restore ctx around each operation.
- For large whiteboards, use OffscreenCanvas + Worker to avoid blocking the main thread during heavy drawing.
Revisions (0)
No revisions yet.