Scene Utilities
Utilities for managing scenes, element collections, and scene state in Excalidraw.
Scene Class
The Scene class is the core container for managing Excalidraw elements and their state.
Constructor
Creates a new Scene instance.
class Scene {
constructor(
elements?: ElementsMapOrArray | null,
options?: {
skipValidation?: true;
}
);
}
elements
ElementsMapOrArray | null
Initial elements (array or Map)
Skip validation of fractional indices
Example:
import { Scene } from "@excalidraw/excalidraw";
const scene = new Scene([
/* elements */
]);
Scene Methods
getElementsIncludingDeleted
Returns all elements including deleted ones.
getElementsIncludingDeleted(): readonly OrderedExcalidrawElement[]
All elements in the scene
getNonDeletedElements
Returns only non-deleted elements.
getNonDeletedElements(): readonly NonDeleted<ExcalidrawElement>[]
elements
NonDeleted<ExcalidrawElement>[]
All non-deleted elements
getNonDeletedElementsMap
Returns a Map of non-deleted elements indexed by ID.
getNonDeletedElementsMap(): NonDeletedSceneElementsMap
map
Map<string, ExcalidrawElement>
Map of element IDs to elements
Example:
const elementsMap = scene.getNonDeletedElementsMap();
const element = elementsMap.get("element-id-123");
getElement
Gets a specific element by ID (including deleted).
getElement<T extends ExcalidrawElement>(id: string): T | null
The element or null if not found
getNonDeletedElement
Gets a non-deleted element by ID.
getNonDeletedElement(id: string): NonDeleted<ExcalidrawElement> | null
element
NonDeleted<ExcalidrawElement> | null
The non-deleted element or null
getSelectedElements
Returns currently selected elements.
getSelectedElements(opts: {
selectedElementIds: AppState["selectedElementIds"];
elements?: ElementsMapOrArray;
includeBoundTextElement?: boolean;
includeElementsInFrames?: boolean;
}): NonDeleted<ExcalidrawElement>[]
opts.selectedElementIds
Map<string, true>
required
Map of selected element IDs
Custom elements to use (defaults to scene elements)
opts.includeBoundTextElement
Include text bound to selected containers
opts.includeElementsInFrames
Include elements inside selected frames
elements
NonDeleted<ExcalidrawElement>[]
Selected elements
Example:
const selectedElements = scene.getSelectedElements({
selectedElementIds: appState.selectedElementIds,
includeBoundTextElement: true,
});
insertElement
Inserts a single element into the scene.
insertElement(element: ExcalidrawElement): void
element
ExcalidrawElement
required
Element to insert
Example:
import { newElement } from "@excalidraw/excalidraw";
const rectangle = newElement({
type: "rectangle",
x: 100,
y: 100,
width: 200,
height: 150,
});
scene.insertElement(rectangle);
insertElements
Inserts multiple elements into the scene.
insertElements(elements: ExcalidrawElement[]): void
elements
ExcalidrawElement[]
required
Elements to insert
insertElementAtIndex
Inserts an element at a specific index in the z-order.
insertElementAtIndex(element: ExcalidrawElement, index: number): void
element
ExcalidrawElement
required
Element to insert
Z-index position (0 = bottom)
replaceAllElements
Replaces all elements in the scene.
replaceAllElements(
elements: ElementsMapOrArray,
options?: { skipValidation?: true }
): void
elements
ElementsMapOrArray
required
New elements (array or Map)
Skip fractional index validation
Example:
scene.replaceAllElements(newElements);
mapElements
Maps over all elements, optionally modifying them.
mapElements(
iteratee: (element: ExcalidrawElement) => ExcalidrawElement
): boolean
Function that receives each element and returns the element (modified or unchanged)
Whether any elements were modified
Example:
// Move all elements 10px to the right
scene.mapElements((element) => ({
...element,
x: element.x + 10,
}));
mutateElement
Mutates a single element and triggers scene update.
mutateElement<TElement extends ExcalidrawElement>(
element: TElement,
updates: ElementUpdate<TElement>,
options?: {
informMutation: boolean;
isDragging: boolean;
}
): TElement
element
ExcalidrawElement
required
Element to mutate
updates
Partial<TElement>
required
Properties to update
Whether to trigger scene update
Whether element is being dragged
Example:
scene.mutateElement(element, {
x: 150,
y: 200,
backgroundColor: "#ff0000",
});
getElementIndex
Gets the z-index of an element.
getElementIndex(elementId: string): number
Z-index of the element (-1 if not found)
getContainerElement
Gets the container element for a bound element.
getContainerElement(
element: ExcalidrawElement & { containerId: string | null } | null
): ExcalidrawElement | null
element
ExcalidrawElement
required
Element with containerId property
The container element or null
getSceneNonce
Gets a random nonce that changes with each scene update.
getSceneNonce(): number | undefined
Random integer regenerated on each update
Usage: Used for cache invalidation in renderers.
onUpdate
Registers a callback for scene updates.
onUpdate(callback: () => void): () => void
Function to call on scene updates
Function to unregister the callback
Example:
const unsubscribe = scene.onUpdate(() => {
console.log("Scene updated!");
renderScene();
});
// Later: unsubscribe when no longer needed
unsubscribe();
triggerUpdate
Manually triggers scene update callbacks.
destroy
Cleans up the scene and removes all elements.
Example:
Scene Version Functions
getSceneVersion
Calculates scene version by summing element versions.
function getSceneVersion(elements: readonly ExcalidrawElement[]): number
elements
readonly ExcalidrawElement[]
required
Elements to calculate version from
Sum of all element versions
Note: Deprecated in favor of hashElementsVersion.
hashElementsVersion
Generates a hash of elements’ versionNonce values.
function hashElementsVersion(elements: ElementsMapOrArray): number
elements
ElementsMapOrArray
required
Elements to hash (array or Map)
Unsigned 32-bit integer hash
Example:
import { hashElementsVersion } from "@excalidraw/excalidraw";
const currentHash = hashElementsVersion(scene.getNonDeletedElements());
if (currentHash !== previousHash) {
// Scene has changed
renderScene();
}
Complete Scene Example
import {
Scene,
newElement,
newTextElement,
hashElementsVersion,
} from "@excalidraw/excalidraw";
class SceneManager {
private scene: Scene;
private lastHash: number = 0;
constructor() {
this.scene = new Scene();
// Listen for changes
this.scene.onUpdate(() => {
this.onSceneUpdate();
});
}
addRectangle(x: number, y: number, width: number, height: number) {
const rectangle = newElement({
type: "rectangle",
x,
y,
width,
height,
strokeColor: "#000000",
backgroundColor: "#ffffff",
});
this.scene.insertElement(rectangle);
}
addText(x: number, y: number, text: string) {
const textElement = newTextElement({
text,
x,
y,
fontSize: 20,
});
this.scene.insertElement(textElement);
}
moveSelectedElements(dx: number, dy: number, selectedIds: Set<string>) {
this.scene.mapElements((element) => {
if (selectedIds.has(element.id)) {
return {
...element,
x: element.x + dx,
y: element.y + dy,
};
}
return element;
});
}
deleteSelectedElements(selectedIds: Set<string>) {
this.scene.mapElements((element) => {
if (selectedIds.has(element.id)) {
return {
...element,
isDeleted: true,
};
}
return element;
});
}
onSceneUpdate() {
const currentHash = hashElementsVersion(
this.scene.getNonDeletedElements()
);
if (currentHash !== this.lastHash) {
console.log("Scene changed, re-rendering...");
this.lastHash = currentHash;
this.render();
}
}
render() {
const elements = this.scene.getNonDeletedElements();
console.log(`Rendering ${elements.length} elements`);
// Render logic here
}
getElements() {
return this.scene.getNonDeletedElements();
}
getElementById(id: string) {
return this.scene.getNonDeletedElement(id);
}
clear() {
this.scene.replaceAllElements([]);
}
destroy() {
this.scene.destroy();
}
}
// Usage
const manager = new SceneManager();
manager.addRectangle(100, 100, 200, 150);
manager.addText(150, 175, "Hello World");
const elements = manager.getElements();
console.log(`Total elements: ${elements.length}`);
See Also