From Scene to Screen: Why the Hand-Drawn Editor Feels Fast
Excalidraw’s rendering problem is to make structured scene records feel like an immediate, hand-drawn surface without turning temporary interface state into document data. This chapter traces that path: stable scene content is rendered through one path, selection and collaboration feedback through another, and expensive shape work through caches that can be rebuilt when their assumptions change. The result is not a universal performance promise, but a clear way to understand what the editor optimizes and where the browser still sets the limits.
The scene already contains the information needed to render a rectangle, a bound label, an arrow, or an image. Rendering adds the transformations that make those records visible: zoom, scrolling, frame clipping, rotation, text measurement, image lookup, theme treatment, and interaction feedback. Those transformations matter because the same element must remain a durable record even as the user zooms, drags, selects, or collaborates.
Two paths share one scene
The static scene is the content layer. It prepares the canvas, applies zoom, optionally draws a grid, walks through visible elements, and invokes the element renderer. It can clip elements to frames, defer bound text until its container is drawn, render ordinary elements before embeddable content, and add link indicators when appropriate. Its job is to make the drawing itself visible in the correct order.
The interactive scene is the feedback layer. It draws the selection rectangle, resize and rotation handles, linear-element points, binding highlights, midpoint indicators, remote selections, collaborators’ cursors, snapping guides, crop handles, and scrollbars. These marks answer questions about the current interaction: what is selected, where an arrow could bind, which user is moving an object, or how a crop will change. They are not part of the scene’s durable meaning.
That separation prevents a common category error. If a user selects a rectangle, the selection border should appear immediately but should not become another rectangle in the saved file. If a collaborator’s cursor moves, the cursor should be visible without modifying the shared diagram. If a user hovers near an arrow endpoint, the possible binding can be highlighted before the binding is committed. The editor can therefore respond quickly to interaction while preserving the scene as a clean document.
The two paths still coordinate. Both use the same scene coordinates, scroll offsets, zoom, element bounds, and relationships. The static layer may clip an element to its containing frame; the interactive layer must place a selection border or binding hint in the same transformed space. A mismatch would make handles drift away from shapes or make remote selections appear to belong to the wrong object. The renderer consequently treats the scene as a common geometric reference while allowing each canvas to draw a different kind of result.
Consider dragging an arrow toward a box. The static scene continues to show the existing arrow and box. The interactive scene can highlight the box, show possible binding points, and expose the arrow’s editable endpoint. Only when the user completes the action does the scene record acquire or change a binding. A pointer move can affect several frames of visual feedback without creating a durable document mutation. That is the rendering consequence of separating content from interaction.
The split also gives each path a different update rhythm. Scene content can be redrawn when an element changes, a frame is clipped differently, or a cached bitmap must be rebuilt. Interaction feedback can change on every pointer move, animation tick, or collaborator update. If those signals were stored in the same records, a high-frequency cursor or hover state could invalidate document state and force unrelated consumers to process it. Keeping the overlay transient limits that coupling while still allowing it to use the scene’s geometry.
This is not the same as saying that the static layer never changes during interaction. A drag can mutate the element being moved, and the static content may need to reflect that new geometry. The useful distinction is ownership: the scene owns a committed or in-progress element value, while the interactive layer owns indications about the action around that value. A selected object can therefore be both a scene element and the subject of an overlay, without the overlay becoming another scene element.
The same principle applies to bound text. A label may be represented as its own text element because it needs text-specific properties, but the renderer knows that its container determines its placement and available width. It can draw the container and then the label in the right relationship without flattening them into a single bitmap. This lets the scene preserve editability while the screen presents the pair as one coherent visual object.
The rendering path also has to distinguish ordinary shapes from images and embeds. A ready image can be drawn from the file cache; a pending or missing file can receive a placeholder rather than freezing the entire scene. An embeddable element may require validation or a host-provided rendering decision, so the renderer places such content in a controlled phase and can provide a label when the embedded content is not being rendered. The scene remains the same even when the current browser cannot display every external payload.
The flow shows why “the renderer” is not one operation. The scene supplies durable input. Static preparation determines what content is visible and how it is transformed. Cached element canvases reduce repeated drawing work for the interactive viewport. Overlay rendering adds transient UI. Export can take a different route when a portable result needs direct rendering rather than a screenshot of an interactive cache.
Deterministic style, useful caches, and export trade-offs
Excalidraw’s hand-drawn character begins with a deliberate data choice: each element carries a seed used by the Rough.js-based shape generator. Without a stable seed, the same rectangle could receive a different irregular outline whenever it was rendered. That would make the scene visually unstable during zooming, selection, local restoration, or collaboration. The seed does not turn the shape into a raster image; it gives the renderer a repeatable input from which to generate the appearance.
The renderer then caches the result of drawing individual elements. A cached canvas can be placed into the viewport instead of rebuilding every shape from scratch on every frame. This is especially useful when most of a large scene is unchanged while one object is moving or an overlay is being updated. The cache is not the document. It is a derived bitmap whose validity depends on the element, the rendering environment, and the current transformation.
That dependency explains cache invalidation. A geometry change can alter the shape and its bounds. A changed image file or crop changes the visible payload. A theme change alters colors. A zoom change can require a different cached scale. A containing frame’s opacity can affect the effective appearance of an element. The renderer tracks relevant conditions and regenerates an element canvas when the old one no longer represents the current scene projection. Mutation also removes shape-cache entries when changes affect dimensions, points, or image references.
Caching has a cost as well as a benefit. Each cached canvas consumes memory and may become expensive to create if an element is very large. Device-pixel scaling can make a modest scene-space object occupy a much larger bitmap on a high-density display. Rotated or transformed content may need resampling, and a cache created at one zoom level may be less sharp or more expensive at another. The renderer therefore caps cached canvas dimensions and total area using browser-oriented safeguards. Those safeguards reduce the chance that one extreme element will exhaust the browser’s canvas limits, but they do not establish a universal maximum scene size or a guaranteed frame rate.
The failure path is part of this performance design. If an element cannot produce a usable cached canvas, the renderer can skip that drawing attempt or report the error without invalidating every other record. Images can remain visible as placeholders while their files are pending. A huge line or arrow can be handled during restoration before its geometry produces an unreasonable render workload. These choices preserve the rest of the scene, but they also mean that a user may see incomplete content or a degraded representation rather than a perfect picture. Resilience here means containing the failure, not concealing it.
The visual path also has to handle different element geometries. A rectangle can be drawn as a shape. A line or arrow is a sequence of points that may include arrowheads and bindings. Free-draw content can be rendered from a path and pressure data. Text requires font selection, line-height handling, alignment, and sometimes temporary DOM attachment for correct text measurement or directionality. Images require a file cache, crop coordinates, scale, and placeholder behavior. These are not separate document models; they are element-specific renderers operating over a common record shape.
Zoom and device pixels create another boundary. Scene coordinates describe the drawing independently of the viewport. The renderer combines them with scroll position, zoom, device-pixel ratio, and canvas dimensions. At some scales, smoothing improves the appearance of a resampled cached bitmap; at others, snapping an eligible bitmap to device pixels keeps text and strokes sharp. The implementation treats these as rendering conditions, not as changes to the element’s stored geometry. A user can zoom without rewriting every object in the document.
Frames make the same point through clipping. An element’s frameId expresses membership in the scene. The static renderer can apply a frame clip when frame rendering is enabled, while the interactive layer can highlight the frame or show its editing boundaries. The element still retains its own coordinates and relationships. A frame is a visual and organizational boundary applied during projection, not a reason to flatten all of its children into one image.
Export changes the optimization target. The live editor favors responsiveness and may draw from cached element canvases. An export needs a self-contained, predictable result whose bounds, fonts, background, theme, images, and frame behavior are controlled. Canvas export calculates the content area and renders the scene into a new canvas. SVG export constructs an SVG root, can inline font declarations, draws vector-oriented scene content, and can embed a compressed scene payload in metadata when requested. The export path therefore renders from the scene rather than simply copying whatever pixels happen to be present in the interactive viewport.
That difference is important for fidelity and scale. A screenshot of a cached viewport would preserve current zoom, selection mistakes, or missing overlay state. A deliberate export can omit selection handles, choose a background, include frame labels, load the needed fonts, and preserve an editable scene payload separately from the visible SVG. It also has its own limitations: external embeds may not be rendered, images may still depend on available files, and the browser remains responsible for the underlying canvas and font APIs.
Export also makes the distinction between visual equivalence and document equivalence visible. Two exports can look similar while carrying different information: a PNG has pixels but no editable element relationships; an SVG can carry vector shapes and, when configured, an embedded scene payload; a JSON export preserves structured elements but is not itself a ready-to-display image. The renderer must decide which information the destination supports and which information belongs in metadata or a companion file. That is why export is a projection of the scene, not simply a request to dump the current canvas.
The architecture’s strength is therefore conditional. The split between static content and interactive overlays works when both paths honor the same scene geometry. Deterministic seeds work when the renderer receives the same element data and compatible rendering behavior. Caches help when most content is stable and invalidation is accurate. Direct export helps when the caller needs a portable artifact rather than a live viewport. None of these choices removes browser memory limits, image-loading failures, font differences, or the cost of complex scenes.
This makes the hand-drawn style an architectural feature rather than a decorative skin. The appearance is generated from structured records, stabilized by stored seeds, accelerated through derived caches, and reinterpreted for export. The next question is what happens when those records leave the live rendering loop: how restoration, open serialization, image payloads, libraries, and exported artifacts preserve the same scene across different destinations.
References
- Static scene renderer
- Interactive scene renderer
- Element renderer
- Element seed and visual data
- Core package dependencies
- JSON serialization and loading
- Exported data structures
- Canvas and SVG export implementation
- Package exports
- Project feature overview
- Restore safeguards
- Local persistence implementation
- Reconciliation rules
- Hosted-app persistence