Infinite Canvas
Overview
Section titled “Overview”@xnet/canvas provides an infinite, zoomable 2D surface where users can place, connect, and arrange nodes. The canvas supports real-time collaboration via Yjs — multiple users can simultaneously add, move, and connect nodes.
Canvas component
Section titled “Canvas component”import { Canvas } from '@xnet/canvas'
;<Canvas doc={ydoc} // Required: Yjs document awareness={awareness} // Cursor presence renderNode={(node) => <MyCard />} // Custom node content onNodeDoubleClick={(id) => open(id)} onBackgroundClick={() => deselect()} config={{ minZoom: 0.1, maxZoom: 3, gridSize: 20, showGrid: true }}/>Keyboard shortcuts
Section titled “Keyboard shortcuts”| Shortcut | Action |
|---|---|
Delete / Backspace | Delete selected nodes |
Cmd+A | Select all |
Escape | Clear selection |
Cmd+1 | Fit to content |
Cmd+0 | Reset view |
Mouse interaction
Section titled “Mouse interaction”- Scroll — Pan the canvas
- Ctrl/Cmd + Scroll — Zoom (pinch-to-zoom)
- Click node — Select it
- Shift/Cmd + Click — Additive selection
- Drag node — Move it
- Drag resize handles — Resize (8 handles on selection)
- Double-click — Open node
useCanvas hook
Section titled “useCanvas hook”The primary hook that wires together the store, viewport, and layout engine:
const { // State nodes, edges, selectedNodeIds, viewport,
// Node operations addNode, updateNodePosition, removeNode,
// Edge operations addEdge, removeEdge,
// Selection selectNode, selectAll, clearSelection, deleteSelected,
// Viewport pan, zoomAt, fitToContent, resetView,
// Layout autoLayout, layoutSelected,
// Queries findNodeAt, findNodesInRect, getVisibleNodes} = useCanvas({ doc: ydoc, config })Spatial index
Section titled “Spatial index”The canvas uses an R-tree (rbush) for efficient spatial queries. This enables viewport culling — only nodes visible on screen need to be rendered.
// Find all nodes in the current viewportconst visibleIds = store.getVisibleNodes(viewport.getVisibleRect())
// Find the topmost node at a pointconst nodeId = store.findNodeAt(point)
// Find all nodes overlapping with a given nodeconst overlapping = store.findIntersecting(nodeId)The spatial index is automatically updated when nodes are added, moved, or removed.
Layout algorithms
Section titled “Layout algorithms”The LayoutEngine supports automatic layout via ELK.js and two built-in algorithms:
ELK.js layouts
Section titled “ELK.js layouts”await autoLayout({ algorithm: 'layered', // Sugiyama/hierarchical direction: 'RIGHT', // RIGHT, LEFT, DOWN, UP nodeSpacing: 50, layerSpacing: 100, edgeRouting: 'ORTHOGONAL' // POLYLINE, ORTHOGONAL, SPLINES})Available algorithms: layered, force, mrtree, radial, stress, box.
Grid layout
Section titled “Grid layout”layoutEngine.layoutGrid(nodes, { columns: 4, spacing: 20, padding: 50})Circle layout
Section titled “Circle layout”layoutEngine.layoutCircle(nodes, { radius: 300 // Auto-calculated if omitted})Nodes with properties.fixed = true keep their positions during layout.
Viewport
Section titled “Viewport”The Viewport class manages the camera:
pan(dx, dy)— Move by screen-space deltazoomAt(x, y, factor)— Zoom while keeping the point under cursor stationaryfitToRect(rect, padding)— Fit viewport to show a rectangle (never zooms beyond 100%)reset()— Return to origin at zoom 1screenToCanvas(x, y)— Convert screen coordinates to canvas spacecanvasToScreen(x, y)— Convert canvas coordinates to screen spacegetVisibleRect()— The canvas-space rectangle currently on screen
Data storage
Section titled “Data storage”Canvas state is stored in three Yjs maps inside the Y.Doc:
| Map | Contents |
|---|---|
metadata | Title, created/updated timestamps |
nodes | CanvasNode objects keyed by ID |
edges | CanvasEdge objects keyed by ID |
All mutations go through Yjs transactions, so changes sync automatically to other peers. The spatial index updates reactively when the Yjs maps change.
Node types
Section titled “Node types”interface CanvasNode { id: string type: string // 'page', 'database', 'note', etc. position: { x: number y: number width: number // Default: 200 height: number // Default: 100 rotation?: number zIndex?: number collapsed?: boolean } properties: Record<string, unknown> linkedNodeId?: string // Link to a page/database node}Edges connect nodes with optional labels, arrow markers, and routing styles (bezier or orthogonal).
Comments
Section titled “Comments”The canvas supports spatial comments via useCanvasComments:
- Position pins — Fixed canvas coordinates. Never orphaned.
- Object-attached pins — Follow a node’s position. Become orphaned when the node is deleted.
Further reading
Section titled “Further reading”- Collaboration Guide — Real-time editing patterns
- Sync Guide — How Yjs sync works