Browse Source

perf: add requestAnimationFrame debouncing to BaseNode ResizeObserver

Wrap the ResizeObserver callback in requestAnimationFrame with
cancellation to coalesce rapid resize events into one setNodes call
per frame. Cleanup cancels any pending RAF on unmount.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
b15c1e94ae
  1. 43
      src/components/nodes/BaseNode.tsx

43
src/components/nodes/BaseNode.tsx

@ -100,27 +100,36 @@ export function BaseNode({
const panelEl = settingsPanelRef.current; const panelEl = settingsPanelRef.current;
if (!panelEl) return; if (!panelEl) return;
let rafId: number | null = null;
const observer = new ResizeObserver((entries) => { const observer = new ResizeObserver((entries) => {
for (const entry of entries) { if (rafId !== null) cancelAnimationFrame(rafId);
const newPanelHeight = entry.contentRect.height; rafId = requestAnimationFrame(() => {
if (newPanelHeight === 0) continue; rafId = null;
const delta = newPanelHeight - trackedSettingsHeightRef.current; for (const entry of entries) {
if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes const newPanelHeight = entry.contentRect.height;
if (newPanelHeight === 0) continue;
trackedSettingsHeightRef.current = newPanelHeight; const delta = newPanelHeight - trackedSettingsHeightRef.current;
setNodes((nodes) => if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes
nodes.map((node) => {
if (node.id !== id) return node; trackedSettingsHeightRef.current = newPanelHeight;
const currentHeight = getNodeDimension(node, "height"); setNodes((nodes) =>
const newHeight = Math.max(minHeight, currentHeight + delta); nodes.map((node) => {
return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight); if (node.id !== id) return node;
}) const currentHeight = getNodeDimension(node, "height");
); const newHeight = Math.max(minHeight, currentHeight + delta);
} return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight);
})
);
}
});
}); });
observer.observe(panelEl); observer.observe(panelEl);
return () => observer.disconnect(); return () => {
if (rafId !== null) cancelAnimationFrame(rafId);
observer.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [settingsExpanded, settingsPanel]); }, [settingsExpanded, settingsPanel]);

Loading…
Cancel
Save