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;
if (!panelEl) return;
let rafId: number | null = null;
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const newPanelHeight = entry.contentRect.height;
if (newPanelHeight === 0) continue;
const delta = newPanelHeight - trackedSettingsHeightRef.current;
if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes
trackedSettingsHeightRef.current = newPanelHeight;
setNodes((nodes) =>
nodes.map((node) => {
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);
})
);
}
if (rafId !== null) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
rafId = null;
for (const entry of entries) {
const newPanelHeight = entry.contentRect.height;
if (newPanelHeight === 0) continue;
const delta = newPanelHeight - trackedSettingsHeightRef.current;
if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes
trackedSettingsHeightRef.current = newPanelHeight;
setNodes((nodes) =>
nodes.map((node) => {
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);
return () => observer.disconnect();
return () => {
if (rafId !== null) cancelAnimationFrame(rafId);
observer.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [settingsExpanded, settingsPanel]);

Loading…
Cancel
Save