Part VI β The Blueprint Β· Lesson 11.9
HLD β Diagram Tools (Excalidraw)
In one lineA canvas render loop plus an operation log β undo, collaboration, and persistence all come from the log.
Where you've seen itDragging a shape smoothly while someone else edits the same diagram in real time.
Diagram
1β2 Β· SCOPE AND SCALE
in scope draw shapes Β· select/move/resize Β· undo Β· export Β· collab
out version history UI Β· comments Β· templates Β· auth
scale ~5k elements per board Β· 60fps interaction required
2β10 concurrent editors3 Β· WHY CANVAS, NOT DOM
5,000 shapes as DOM nodes β layout + paint per frame, ~10fps
5,000 shapes on <canvas> β one draw call loop, 60fps
LAYER MODEL β separate canvases so you redraw the smallest thing
ββββββββββββββββββββββββββββββββββββ
β interaction layer cursors, β redraw every frame
β selection box β
ββββββββββββββββββββββββββββββββββββ€
β active layer shape being β redraw during drag
β dragged β
ββββββββββββββββββββββββββββββββββββ€
β static layer everything β redraw only on commit
β else β
ββββββββββββββββββββββββββββββββββββ
Dragging one shape must never redraw the other 4,999.6a Β· DEEP DIVE β THE OPERATION LOG POWERS EVERYTHING
every change becomes an operation
{ id:'op91', type:'move', elementId:'e7',
from:{x:10,y:20}, to:{x:80,y:60}, author:'u1', seq:412 }
ββββββββββββββββ
β operation logβ
ββββββββ¬ββββββββ
βββββββββββΌββββββββββ
βΌ βΌ βΌ
UNDO SYNC PERSIST
invert & broadcast append-only,
re-apply to peers replay to
rebuild state
One structure, three features. Storing only final state gives
you none of them.6b Β· DEEP DIVE β COLLABORATION AND SPATIAL LOOKUP
collab WebSocket (2.4) Β· per-element last-write-wins is
adequate here (unlike text β see 11.11)
cursors broadcast at ~20Hz, throttled, never persisted
hit-test naive: check 5,000 shapes per mousemove β
spatial index (quadtree/R-tree): check ~10 β
rebuild the index on commit, not during dragHow it works
- Canvas with layered surfaces, redrawing only the layer that changed.
- Model elements as plain data (type, x, y, w, h, style, version) β rendering is a pure function of that array.
- Every mutation is an operation appended to a log; undo inverts and re-applies.
- Spatial index for hit testing and viewport culling β draw only what's on screen.
requestAnimationFramerender loop, coalescing pointer events so a fast mouse doesn't trigger extra frames.
Trade-offs
| Choice | Cost |
|---|---|
| Canvas | Fast; you rebuild accessibility and text selection |
| SVG/DOM | Free a11y and hit testing; dies past ~1k nodes |
| Operation log | Undo + sync + persistence; more memory |
| Last-write-wins | Simple; occasional lost edit on the same shape |
Accessibility (8.1): canvas is opaque to screen readers. Maintain a parallel DOM list of elements, keyboard shortcuts for select/move, and text alternatives β this is the honest weakness of canvas apps and worth naming yourself.
Interview angle
"Dragging gets slow with 5,000 shapes. What's wrong?"
- You're probably redrawing the entire canvas per pointer event β split into static and active layers so a drag redraws one shape.
- Hit testing is likely linear per mousemove; add a quadtree and cull to the viewport.
- Coalesce pointer events into one
requestAnimationFrametick; browsers deliver far more of them than you can render.
Recap
- Layered canvases: redraw the smallest surface that changed.
- The operation log gives you undo, collaboration, and persistence from one structure.
- Spatial indexing turns hit testing from O(n) into O(log n).