From e5eaa4f8d7caba2108f25e912d8763190ce77b14 Mon Sep 17 00:00:00 2001 From: ruiny Date: Tue, 2 Jun 2026 13:49:21 +0530 Subject: [PATCH] perf: eliminate layout thrashing and array allocations during drawing (#14) --- src/client/components/EditorCanvas.tsx | 3 ++- src/client/hooks/useThemeTokenSync.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/client/components/EditorCanvas.tsx b/src/client/components/EditorCanvas.tsx index c5f4844..9876db0 100644 --- a/src/client/components/EditorCanvas.tsx +++ b/src/client/components/EditorCanvas.tsx @@ -28,7 +28,8 @@ function sceneFromEditor( files: BinaryFiles, ): ScenePayload { return { - elements: [...elements], + // Excalidraw passes immutable arrays, avoid copying it on every onChange event + elements: elements as unknown[], appState: appState as unknown as Record, files: files as unknown as Record, }; diff --git a/src/client/hooks/useThemeTokenSync.ts b/src/client/hooks/useThemeTokenSync.ts index 2e48ea7..30cda88 100644 --- a/src/client/hooks/useThemeTokenSync.ts +++ b/src/client/hooks/useThemeTokenSync.ts @@ -33,10 +33,20 @@ export function useThemeTokenSync( return; } + // Read all tokens first to avoid layout thrashing const computedStyles = window.getComputedStyle(excalidrawRoot); + const updates: Array<[string, string]> = []; + for (const [sourceToken, targetToken] of EXCALIDRAW_THEME_TOKEN_MAP) { const value = computedStyles.getPropertyValue(sourceToken).trim(); if (value) { + updates.push([targetToken, value]); + } + } + + // Write in a separate pass, skipping unchanged values + for (const [targetToken, value] of updates) { + if (appShell.style.getPropertyValue(targetToken) !== value) { appShell.style.setProperty(targetToken, value); } }