The Scene class is the central data structure in Excalidraw that manages all elements in a drawing. It provides a reactive system for element storage, retrieval, mutation, and change tracking, serving as the single source of truth for the canvas state.
import { Scene } from "@excalidraw/element";// Create empty sceneconst scene = new Scene();// Create scene with initial elementsconst scene = new Scene(elements);// Create scene with elements mapconst elementsMap = new Map(elements.map(el => [el.id, el]));const scene = new Scene(elementsMap);// Skip fractional index validation (for performance)const scene = new Scene(elements, { skipValidation: true });
// Get all elements including deletedconst allElements = scene.getElementsIncludingDeleted();const allElementsMap = scene.getElementsMapIncludingDeleted();// Get non-deleted elements onlyconst activeElements = scene.getNonDeletedElements();const activeElementsMap = scene.getNonDeletedElementsMap();// Get frame elementsconst allFrames = scene.getFramesIncludingDeleted();const activeFrames = scene.getNonDeletedFramesLikes();
// Get element by ID (returns null if not found)const element = scene.getElement<ExcalidrawRectangleElement>("elementId");// Get non-deleted element (returns null if deleted or not found)const activeElement = scene.getNonDeletedElement("elementId");// Get element index in arrayconst index = scene.getElementIndex("elementId");
// Get container element for bound textconst container = scene.getContainerElement(textElement);// Get elements by ID or group IDconst elements = scene.getElementsFromId("idOrGroupId");// Returns element if ID matches, or all elements in group if group ID matches
scene.replaceAllElements(newElements);// Accepts array or Map of elements// Automatically:// - Syncs invalid fractional indices// - Updates internal maps and arrays// - Separates frames from other elements// - Filters deleted vs non-deleted elements// - Triggers update callbacks
replaceAllElements validates fractional indices by default. For bulk updates where indices are already valid, use { skipValidation: true } for better performance.
// Insert single element at specific indexscene.insertElementAtIndex(element, 5);// Insert multiple elements at indexscene.insertElementsAtIndex([element1, element2], 10);// Insert element (auto-positions based on frameId)scene.insertElement(element);// If element has frameId, inserts at frame's index// Otherwise, appends to end// Insert multiple elementsscene.insertElements([element1, element2, element3]);
Insert methods automatically sync fractional indices using syncMovedIndices to maintain proper ordering.
Mutate elements in place while triggering scene updates:
packages/element/src/Scene.ts
Copy
scene.mutateElement( element, { x: 200, y: 300, width: 400, }, { informMutation: true, // Trigger scene update (default: true) isDragging: false, // Whether element is being dragged });// Returns the mutated element// Automatically:// - Updates version and versionNonce// - Updates timestamp// - Triggers scene callbacks if informMutation is true
When to Use informMutation: false
Set informMutation: false when:
Batching multiple mutations and want a single update at the end
Making temporary changes that will be reverted
Updating elements that aren’t in the scene (e.g., during element creation)
The Scene caches selected elements for performance:
packages/element/src/Scene.ts
Copy
const selectedElements = scene.getSelectedElements({ selectedElementIds: appState.selectedElementIds, // Optional: use custom elements instead of scene elements elements: customElementsArray, // Selection options includeBoundTextElement: true, includeElementsInFrames: false,});
Selection Caching
Selection Options
The Scene maintains a sophisticated selection cache:
// Create sceneconst scene = new Scene(elements);// Use scenescene.replaceAllElements(newElements);const unsubscribe = scene.onUpdate(callback);// Cleanup when donescene.destroy();// Clears all elements, maps, and callbacks// Prevents memory leaks and late callback fires
Always call scene.destroy() when disposing of a Scene instance to prevent memory leaks.
// ✅ Good: Use maps for ID lookupsconst element = scene.getElement(id);const map = scene.getElementsMapIncludingDeleted();const element = map.get(id);// ❌ Bad: Linear search through arrayconst elements = scene.getElementsIncludingDeleted();const element = elements.find(el => el.id === id);
// ✅ Good: Reuse same options object for cache hitsconst options = { selectedElementIds: appState.selectedElementIds, includeBoundTextElement: true, includeElementsInFrames: false,};const selected1 = scene.getSelectedElements(options);const selected2 = scene.getSelectedElements(options); // Cache hit!// ❌ Bad: New options object each time (cache miss)const selected = scene.getSelectedElements({ selectedElementIds: appState.selectedElementIds, includeBoundTextElement: true,});
Copy
// ✅ Good: Skip validation for trusted datascene.replaceAllElements(elements, { skipValidation: true });// ⚠️ Default: Validates fractional indices (throttled)scene.replaceAllElements(elements);// Validation runs at most once per minute in dev/test