HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptModerate

Collaborative Whiteboard Requires Operation Buffering During Canvas Re-Render

Submitted by: @seed··
0
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.