Skip to content

Commit 44ce937

Browse files
Defer vue node layout calculations on hidden browser tabs (#8805)
## Summary If you load the window in Nodes 2.0, then switch tabs while it is still loading, the position of the nodes is calculated incorrectly due to useElementBounding returning left=0, top=0 for the canvas element in a hidden tab, causing clientPosToCanvasPos to miscalculate node positions from the ResizeObserver measurements ## Changes - **What**: - Store observed elements while document is in hidden state - Re-observe when tab becomes visible ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8805-Defer-vue-node-layout-calculations-on-hidden-browser-tabs-3046d73d365081019ae6c403c0ac6d1a) by [Unito](https://www.unito.io)
1 parent 138fa6a commit 44ce937

File tree

1 file changed

+31
-1
lines changed

1 file changed

+31
-1
lines changed

src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
* Supports different element types (nodes, slots, widgets, etc.) with
99
* customizable data attributes and update handlers.
1010
*/
11-
import { getCurrentInstance, onMounted, onUnmounted, toValue } from 'vue'
11+
import { getCurrentInstance, onMounted, onUnmounted, toValue, watch } from 'vue'
1212
import type { MaybeRefOrGetter } from 'vue'
1313

14+
import { useDocumentVisibility } from '@vueuse/core'
15+
1416
import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
1517
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
1618
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
@@ -59,9 +61,37 @@ const trackingConfigs: Map<string, ElementTrackingConfig> = new Map([
5961
]
6062
])
6163

64+
// Elements whose ResizeObserver fired while the tab was hidden
65+
const deferredElements = new Set<HTMLElement>()
66+
const visibility = useDocumentVisibility()
67+
68+
watch(visibility, (state) => {
69+
if (state !== 'visible' || deferredElements.size === 0) return
70+
71+
// Re-observe deferred elements to trigger fresh measurements
72+
for (const element of deferredElements) {
73+
if (element.isConnected) {
74+
resizeObserver.observe(element)
75+
}
76+
}
77+
deferredElements.clear()
78+
})
79+
6280
// Single ResizeObserver instance for all Vue elements
6381
const resizeObserver = new ResizeObserver((entries) => {
6482
if (useCanvasStore().linearMode) return
83+
84+
// Skip measurements when tab is hidden — bounding rects are unreliable
85+
if (visibility.value === 'hidden') {
86+
for (const entry of entries) {
87+
if (entry.target instanceof HTMLElement) {
88+
deferredElements.add(entry.target)
89+
resizeObserver.unobserve(entry.target)
90+
}
91+
}
92+
return
93+
}
94+
6595
// Canvas is ready when this code runs; no defensive guards needed.
6696
const conv = useSharedCanvasPositionConversion()
6797
// Group updates by type, then flush via each config's handler

0 commit comments

Comments
 (0)