Skip to main content

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)
options.skipValidation
boolean
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[]
elements
ExcalidrawElement[]
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
id
string
required
Element ID to retrieve
element
ExcalidrawElement | null
The element or null if not found

getNonDeletedElement

Gets a non-deleted element by ID.
getNonDeletedElement(id: string): NonDeleted<ExcalidrawElement> | null
id
string
required
Element ID to retrieve
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
opts.elements
ElementsMapOrArray
Custom elements to use (defaults to scene elements)
opts.includeBoundTextElement
boolean
Include text bound to selected containers
opts.includeElementsInFrames
boolean
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
index
number
required
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)
options.skipValidation
boolean
Skip fractional index validation
Example:
scene.replaceAllElements(newElements);

mapElements

Maps over all elements, optionally modifying them.
mapElements(
  iteratee: (element: ExcalidrawElement) => ExcalidrawElement
): boolean
iteratee
function
required
Function that receives each element and returns the element (modified or unchanged)
changed
boolean
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
options.informMutation
boolean
default:"true"
Whether to trigger scene update
options.isDragging
boolean
default:"false"
Whether element is being dragged
element
ExcalidrawElement
The mutated element
Example:
scene.mutateElement(element, {
  x: 150,
  y: 200,
  backgroundColor: "#ff0000",
});

getElementIndex

Gets the z-index of an element.
getElementIndex(elementId: string): number
elementId
string
required
Element ID
index
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
container
ExcalidrawElement | null
The container element or null

getSceneNonce

Gets a random nonce that changes with each scene update.
getSceneNonce(): number | undefined
nonce
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
callback
function
required
Function to call on scene updates
unsubscribe
function
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.
triggerUpdate(): void

destroy

Cleans up the scene and removes all elements.
destroy(): void
Example:
scene.destroy();

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
version
number
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)
hash
number
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