The Rendering System is responsible for drawing all visual elements on the Excalidraw canvas. It implements a dual-canvas architecture where a static canvas renders the actual element shapes, and an interactive canvas renders overlays such as selection handles, transform handles, binding highlights, and remote collaborator cursors. The system is designed for performance through viewport culling, render throttling, and memoization.
For information about how elements are stored and managed before rendering, see Scene and Element Collection. For details on font loading and text metrics used during rendering, see Font Management. For information on the visual alignment guides rendered during dragging, see Snapping System.
Excalidraw uses two overlaid HTML canvas elements to separate static content from interactive overlays. This separation enables performance optimizations where the static canvas only re-renders when elements change, while the interactive canvas updates frequently during user interactions.
Sources: packages/excalidraw/components/canvases/StaticCanvas.tsx1-134 packages/excalidraw/components/canvases/InteractiveCanvas.tsx1-249 packages/excalidraw/scene/Renderer.ts1-158
Both canvas components are React components that use React.memo with custom comparison functions to prevent unnecessary re-renders. They compare sceneNonce, selectionNonce, and relevant AppState properties to determine if a re-render is needed.
| Component | Purpose | Re-render Triggers |
|---|---|---|
StaticCanvas | Renders element shapes | sceneNonce, elementsMap, visibleElements, viewport changes |
InteractiveCanvas | Renders UI overlays | selectionNonce, sceneNonce, selection changes, collaborator updates |
Sources: packages/excalidraw/components/canvases/StaticCanvas.tsx107-131 packages/excalidraw/components/canvases/InteractiveCanvas.tsx219-246
The Renderer class in packages/excalidraw/scene/Renderer.ts manages the filtering and preparation of elements for rendering. It computes which elements are visible in the current viewport and should be drawn.
Sources: packages/excalidraw/scene/Renderer.ts27-148
The getRenderableElements() method is a memoized function that performs viewport culling to reduce the number of elements passed to the rendering pipeline:
isElementInViewport() to test each element against the current viewport boundssceneNonce to avoid redundant calculationsSources: packages/excalidraw/scene/Renderer.ts70-147
The getRenderableElements() function uses memoization to cache its results. The cache key includes:
zoom, offsetLeft, offsetTop, scrollX, scrollY, height, width (viewport parameters)editingTextElement (which element is being edited)newElementId (element being created)sceneNonce (cache invalidation when scene changes)Sources: packages/excalidraw/scene/Renderer.ts99-148
The static canvas renders the actual element shapes using the RoughJS library for hand-drawn aesthetics. This canvas only re-renders when elements change or viewport parameters change.
Sources: packages/excalidraw/renderer/staticScene.ts (referenced but not provided), packages/excalidraw/components/canvases/StaticCanvas.tsx33-75
The StaticCanvas component manages the static canvas element and calls renderStaticScene() in a useEffect hook. Key aspects:
appState.width, appState.height, and device pixel ratioisRenderThrottlingEnabled() to optionally throttle rendersareEqual comparison to prevent unnecessary re-rendersSources: packages/excalidraw/components/canvases/StaticCanvas.tsx33-134
Sources: packages/excalidraw/components/canvases/StaticCanvas.tsx77-105
The interactive canvas renders UI overlays that change frequently during user interactions. It does not render the element shapes themselves, only visual feedback for editing operations.
Sources: packages/excalidraw/renderer/interactiveScene.ts728-1330
The _renderInteractiveScene() function orchestrates rendering of various interactive overlays:
| Render Function | Purpose | When Rendered |
|---|---|---|
renderLinearPointHandles() | Point handles for lines/arrows | When linear element is selected/editing |
renderSelectionElement() | Drag selection rectangle | During box selection |
renderTextBox() | Text element outline | When editing text without autoResize |
renderBindingHighlight() | Binding target highlight | When isBindingEnabled and suggestedBindings exist |
renderFrameHighlight() | Frame outline highlight | When frameToHighlight is set |
renderElementsBoxHighlight() | Multi-element box highlight | When elementsToHighlight is set |
renderSelectionBorder() | Selection border for elements | For each selected element |
renderTransformHandles() | Resize/rotate handles | For selected elements (single or multiple) |
renderCropHandles() | Image crop handles | When croppingElementId is set |
renderRemoteCursors() | Collaborator cursors | For each active collaborator |
renderSnaps() | Snap alignment lines | When snapLines array has entries |
Sources: packages/excalidraw/renderer/interactiveScene.ts728-1330
Linear elements (lines, arrows) display point handles when selected. The rendering logic handles:
Sources: packages/excalidraw/renderer/interactiveScene.ts434-556
Selection borders are rendered with customizable colors for local and remote selections:
selectionColor (CSS variable --color-selection)#ced4da)Transform handles (for resizing and rotating) are rendered as small rectangles or circles at element corners and edges:
Sources: packages/excalidraw/renderer/interactiveScene.ts558-1105
Remote collaborators are visualized through:
renderRemoteCursors() with collaborator-specific colors and usernamesInteractiveCanvas component before calling render functionSources: packages/excalidraw/components/canvases/InteractiveCanvas.tsx82-123 packages/excalidraw/renderer/interactiveScene.ts914-983
Sources: packages/excalidraw/components/canvases/InteractiveCanvas.tsx184-217
Both static and interactive rendering accept configuration objects that control rendering behavior:
This configuration is built in the InteractiveCanvas component by iterating over appState.collaborators and extracting pointer positions, selected elements, and user metadata.
Sources: packages/excalidraw/components/canvases/InteractiveCanvas.tsx82-149
Sources: packages/excalidraw/scene/types.ts (referenced but not provided in full)
The rendering system employs several strategies to maintain high performance:
Both renderStaticScene and renderInteractiveScene have throttled versions:
The throttled versions use throttleRAF() which ensures rendering happens at most once per animation frame (typically 60 FPS).
Sources: packages/excalidraw/scene/Renderer.ts153-154 packages/excalidraw/components/canvases/InteractiveCanvas.tsx152
getRenderableElements() is memoized with cache invalidation based on viewport and scene parametersStaticCanvas and InteractiveCanvas use React.memo() with custom comparison functionssceneNonce and selectionNonce provide efficient cache invalidation without deep object comparisonsSources: packages/excalidraw/scene/Renderer.ts99-148 packages/excalidraw/components/canvases/StaticCanvas.tsx107-131 packages/excalidraw/components/canvases/InteractiveCanvas.tsx219-246
The isElementInViewport() function tests element bounds against viewport dimensions, preventing off-screen elements from being processed by the rendering pipeline. This is particularly important for large scenes with hundreds or thousands of elements.
Sources: packages/excalidraw/scene/Renderer.ts46-68
RoughJS shape generation is computationally expensive. The rendering system uses a ShapeCache to store pre-generated rough paths, avoiding regeneration on every frame. The cache considers:
shouldCacheIgnoreZoom is false)Sources: Referenced in architecture but implementation in unreferenced files
The bootstrapCanvas() helper function sets up the canvas rendering context with appropriate dimensions and transformations:
This ensures:
scale (typically window.devicePixelRatio)Sources: packages/excalidraw/renderer/interactiveScene.ts748-753
Text rendering requires font metrics for accurate bounding box calculations. The rendering system integrates with the Fonts class (see Font Management) to:
The interactive canvas renders snap lines computed by the snapping system (see Snapping System):
renderSnaps() draws alignment guides when appState.snapLines contains snap dataSources: packages/excalidraw/renderer/interactiveScene.ts69
Binding highlights show when an arrow can bind to a shape:
renderBindingHighlight() draws a translucent highlight around bindable elementsrenderBindingHighlightForBindableElement() handles shape-specific highlighting (rectangle, ellipse, diamond)renderBindingHighlightForSuggestedPointBinding() shows circular binding zones at arrow endpointsSources: packages/excalidraw/renderer/interactiveScene.ts191-354
Refresh this wiki