diff --git a/src/info-panel/PositionManager.ts b/src/info-panel/PositionManager.ts index fedc3a5..c1c772a 100644 --- a/src/info-panel/PositionManager.ts +++ b/src/info-panel/PositionManager.ts @@ -8,6 +8,9 @@ import { StateManager } from './StateManager'; export class PositionManager { stateManager: StateManager; panelElement: HTMLElement; + // Cache the calculated position to avoid getBoundingClientRect() and layout thrashing + // This also ensures controls move in perfect sync with the panel, ignoring CSS transitions + cachedPanelPosition: { x: number, y: number, width: number, height: number } | null = null; constructor(stateManager: StateManager, panelElement: HTMLElement) { this.stateManager = stateManager; @@ -52,6 +55,8 @@ export class PositionManager { this.panelElement.style.left = `${boundedX}px`; this.panelElement.style.top = `${boundedY}px`; + + this.cachedPanelPosition = { x: boundedX, y: boundedY, width: panelWidth, height: panelHeight }; } calculateNormalPosition(): void { @@ -125,6 +130,8 @@ export class PositionManager { this.panelElement.style.left = `${left}px`; this.panelElement.style.top = `${top}px`; + + this.cachedPanelPosition = { x: left, y: top, width: panelWidth, height: panelHeight }; } /** @@ -144,11 +151,26 @@ export class PositionManager { const magnifyGlass = window.comfyUIMagnifyGlass; let referenceRect: DOMRect | null = null; + // 1. Position relative to PANEL (Preferred if visible) if (isPanelVisible && this.panelElement) { - // Position relative to panel - referenceRect = this.panelElement.getBoundingClientRect(); - } else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) { - // Position relative to magnify glass + // Priority: Use Cached Position (Logical) -> Avoids 'chasing' CSS transitions + if (this.cachedPanelPosition) { + // Convert simple object to Rect-like interface for compatibility + referenceRect = { + left: this.cachedPanelPosition.x, + top: this.cachedPanelPosition.y, + right: this.cachedPanelPosition.x + this.cachedPanelPosition.width, + bottom: this.cachedPanelPosition.y + this.cachedPanelPosition.height, + width: this.cachedPanelPosition.width, + height: this.cachedPanelPosition.height + } as DOMRect; + } else { + // Fallback: Read DOM (May cause layout thrashing/lag) + referenceRect = this.panelElement.getBoundingClientRect(); + } + } + // 2. Position relative to GLASS (Fallback if panel hidden) + else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) { referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect(); // Don't show controls if glass is hidden/invalid if (referenceRect.width === 0 || referenceRect.height === 0) { @@ -159,6 +181,9 @@ export class PositionManager { if (!referenceRect) return; + // Enforce no transition on controls to prevent 'rubber-banding' + controlsElement.style.transition = 'none'; + const controlsPosition = this.stateManager.state.settings["🔍MagnifyGlass.ControlsPosition"] || "right"; const margin = 4; diff --git a/web/info-panel/PositionManager.js b/web/info-panel/PositionManager.js index b267ea6..11548f1 100644 --- a/web/info-panel/PositionManager.js +++ b/web/info-panel/PositionManager.js @@ -5,6 +5,9 @@ class PositionManager { constructor(stateManager, panelElement) { __publicField(this, "stateManager"); __publicField(this, "panelElement"); + // Cache the calculated position to avoid getBoundingClientRect() and layout thrashing + // This also ensures controls move in perfect sync with the panel, ignoring CSS transitions + __publicField(this, "cachedPanelPosition", null); this.stateManager = stateManager; this.panelElement = panelElement; } @@ -34,6 +37,7 @@ class PositionManager { const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin)); this.panelElement.style.left = `${boundedX}px`; this.panelElement.style.top = `${boundedY}px`; + this.cachedPanelPosition = { x: boundedX, y: boundedY, width: panelWidth, height: panelHeight }; } calculateNormalPosition() { var _a, _b, _c; @@ -93,6 +97,7 @@ class PositionManager { top = Math.max(4, Math.min(top, window.innerHeight - panelHeight - 4)); this.panelElement.style.left = `${left}px`; this.panelElement.style.top = `${top}px`; + this.cachedPanelPosition = { x: left, y: top, width: panelWidth, height: panelHeight }; } /** * Position the floating controls relative to the panel. @@ -108,7 +113,18 @@ class PositionManager { const magnifyGlass = window.comfyUIMagnifyGlass; let referenceRect = null; if (isPanelVisible && this.panelElement) { - referenceRect = this.panelElement.getBoundingClientRect(); + if (this.cachedPanelPosition) { + referenceRect = { + left: this.cachedPanelPosition.x, + top: this.cachedPanelPosition.y, + right: this.cachedPanelPosition.x + this.cachedPanelPosition.width, + bottom: this.cachedPanelPosition.y + this.cachedPanelPosition.height, + width: this.cachedPanelPosition.width, + height: this.cachedPanelPosition.height + }; + } else { + referenceRect = this.panelElement.getBoundingClientRect(); + } } else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) { referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect(); if (referenceRect.width === 0 || referenceRect.height === 0) { @@ -117,6 +133,7 @@ class PositionManager { } } if (!referenceRect) return; + controlsElement.style.transition = "none"; const controlsPosition = this.stateManager.state.settings["🔍MagnifyGlass.ControlsPosition"] || "right"; const margin = 4; let left; diff --git a/web/info-panel/PositionManager.js.map b/web/info-panel/PositionManager.js.map index 0110c39..3b064fb 100644 --- a/web/info-panel/PositionManager.js.map +++ b/web/info-panel/PositionManager.js.map @@ -1 +1 @@ -{"version":3,"file":"PositionManager.js","sources":["../../src/info-panel/PositionManager.ts"],"sourcesContent":["/**\n * Position Manager class.\n * Handles all positioning logic for the info panel.\n */\n\nimport { StateManager } from './StateManager';\n\nexport class PositionManager {\n stateManager: StateManager;\n panelElement: HTMLElement;\n\n constructor(stateManager: StateManager, panelElement: HTMLElement) {\n this.stateManager = stateManager;\n this.panelElement = panelElement;\n }\n\n positionPanel(): void {\n if (!this.panelElement) return;\n\n // If panel is pinned, keep it at its pinned position\n if (this.stateManager.state.isPanelPinned) {\n this.applyPinnedPosition();\n return;\n }\n\n // Normal positioning logic\n this.calculateNormalPosition();\n }\n\n applyPinnedPosition(): void {\n if (!this.panelElement) return;\n\n let { x, y } = this.stateManager.state.pinnedPosition;\n\n // If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position\n if (x === 0 && y === 0) {\n this.calculateNormalPosition();\n // Save the calculated position as the new pinned position\n if (this.panelElement) {\n const rect = this.panelElement.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n return;\n }\n\n const panelWidth = this.panelElement.offsetWidth || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n const margin = 4;\n\n const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));\n const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));\n\n this.panelElement.style.left = `${boundedX}px`;\n this.panelElement.style.top = `${boundedY}px`;\n }\n\n calculateNormalPosition(): void {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass || !this.panelElement) return;\n\n const settings = this.stateManager.state.settings;\n const panelWidth = Number(settings[\"🔍MagnifyGlass.InfoPanelWidth\"]) || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n\n let left: number;\n let top: number;\n const margin = 4;\n\n // Check for valid glass rect first\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);\n\n // Check for valid mouse position\n const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;\n const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;\n const hasValidMousePosition = mouseX > 0 || mouseY > 0;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass is hidden, use mouse position\n if (hasValidMousePosition) {\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n } else if (hasValidGlassRect) {\n // Position relative to glass\n const position = settings[\"🔍MagnifyGlass.InfoPanelPosition\"];\n switch (position) {\n case \"Right\":\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n case \"Left\":\n left = glassRect!.left - panelWidth - margin;\n top = glassRect!.top;\n break;\n case \"Top\":\n left = glassRect!.left;\n top = glassRect!.top - panelHeight - margin;\n break;\n case \"Bottom\":\n left = glassRect!.left;\n top = glassRect!.bottom + margin;\n break;\n default:\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n }\n } else if (hasValidMousePosition) {\n // Fall back to mouse position\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Final fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n\n left = Math.max(4, Math.min(left, window.innerWidth - panelWidth - 4));\n top = Math.max(4, Math.min(top, window.innerHeight - panelHeight - 4));\n\n this.panelElement.style.left = `${left}px`;\n this.panelElement.style.top = `${top}px`;\n }\n\n /**\n * Position the floating controls relative to the panel.\n */\n positionFloatingControls(controlsElement: HTMLElement | null): void {\n if (!controlsElement) return;\n\n // Check if controls should be visible based on settings\n const settings = this.stateManager.state.settings;\n if (settings[\"🔍MagnifyGlass.ShowHoveringControls\"] === false) {\n controlsElement.style.display = 'none';\n return;\n }\n\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const magnifyGlass = window.comfyUIMagnifyGlass;\n let referenceRect: DOMRect | null = null;\n\n if (isPanelVisible && this.panelElement) {\n // Position relative to panel\n referenceRect = this.panelElement.getBoundingClientRect();\n } else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) {\n // Position relative to magnify glass\n referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect();\n // Don't show controls if glass is hidden/invalid\n if (referenceRect.width === 0 || referenceRect.height === 0) {\n controlsElement.style.display = 'none';\n return;\n }\n }\n\n if (!referenceRect) return;\n\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"right\";\n const margin = 4;\n\n let left: number;\n let top: number;\n\n switch (controlsPosition) {\n case \"left\":\n left = referenceRect.left - controlsElement.offsetWidth - margin;\n top = referenceRect.top;\n break;\n case \"right\":\n left = referenceRect.right + margin;\n top = referenceRect.top;\n break;\n case \"top\":\n left = referenceRect.left;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = referenceRect.left;\n top = referenceRect.bottom + margin;\n break;\n case \"top-right\":\n left = referenceRect.right - controlsElement.offsetWidth;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = referenceRect.right + margin;\n top = referenceRect.top;\n }\n\n // Keep controls within viewport\n left = Math.max(10, Math.min(left, window.innerWidth - controlsElement.offsetWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));\n\n controlsElement.style.left = `${left}px`;\n controlsElement.style.top = `${top}px`;\n\n // Show controls now that they're positioned (initially hidden off-screen)\n controlsElement.style.display = 'flex';\n controlsElement.style.visibility = 'visible';\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"PositionManager.js","sources":["../../src/info-panel/PositionManager.ts"],"sourcesContent":["/**\n * Position Manager class.\n * Handles all positioning logic for the info panel.\n */\n\nimport { StateManager } from './StateManager';\n\nexport class PositionManager {\n stateManager: StateManager;\n panelElement: HTMLElement;\n // Cache the calculated position to avoid getBoundingClientRect() and layout thrashing\n // This also ensures controls move in perfect sync with the panel, ignoring CSS transitions\n cachedPanelPosition: { x: number, y: number, width: number, height: number } | null = null;\n\n constructor(stateManager: StateManager, panelElement: HTMLElement) {\n this.stateManager = stateManager;\n this.panelElement = panelElement;\n }\n\n positionPanel(): void {\n if (!this.panelElement) return;\n\n // If panel is pinned, keep it at its pinned position\n if (this.stateManager.state.isPanelPinned) {\n this.applyPinnedPosition();\n return;\n }\n\n // Normal positioning logic\n this.calculateNormalPosition();\n }\n\n applyPinnedPosition(): void {\n if (!this.panelElement) return;\n\n let { x, y } = this.stateManager.state.pinnedPosition;\n\n // If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position\n if (x === 0 && y === 0) {\n this.calculateNormalPosition();\n // Save the calculated position as the new pinned position\n if (this.panelElement) {\n const rect = this.panelElement.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n return;\n }\n\n const panelWidth = this.panelElement.offsetWidth || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n const margin = 4;\n\n const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));\n const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));\n\n this.panelElement.style.left = `${boundedX}px`;\n this.panelElement.style.top = `${boundedY}px`;\n\n this.cachedPanelPosition = { x: boundedX, y: boundedY, width: panelWidth, height: panelHeight };\n }\n\n calculateNormalPosition(): void {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass || !this.panelElement) return;\n\n const settings = this.stateManager.state.settings;\n const panelWidth = Number(settings[\"🔍MagnifyGlass.InfoPanelWidth\"]) || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n\n let left: number;\n let top: number;\n const margin = 4;\n\n // Check for valid glass rect first\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);\n\n // Check for valid mouse position\n const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;\n const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;\n const hasValidMousePosition = mouseX > 0 || mouseY > 0;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass is hidden, use mouse position\n if (hasValidMousePosition) {\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n } else if (hasValidGlassRect) {\n // Position relative to glass\n const position = settings[\"🔍MagnifyGlass.InfoPanelPosition\"];\n switch (position) {\n case \"Right\":\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n case \"Left\":\n left = glassRect!.left - panelWidth - margin;\n top = glassRect!.top;\n break;\n case \"Top\":\n left = glassRect!.left;\n top = glassRect!.top - panelHeight - margin;\n break;\n case \"Bottom\":\n left = glassRect!.left;\n top = glassRect!.bottom + margin;\n break;\n default:\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n }\n } else if (hasValidMousePosition) {\n // Fall back to mouse position\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Final fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n\n left = Math.max(4, Math.min(left, window.innerWidth - panelWidth - 4));\n top = Math.max(4, Math.min(top, window.innerHeight - panelHeight - 4));\n\n this.panelElement.style.left = `${left}px`;\n this.panelElement.style.top = `${top}px`;\n\n this.cachedPanelPosition = { x: left, y: top, width: panelWidth, height: panelHeight };\n }\n\n /**\n * Position the floating controls relative to the panel.\n */\n positionFloatingControls(controlsElement: HTMLElement | null): void {\n if (!controlsElement) return;\n\n // Check if controls should be visible based on settings\n const settings = this.stateManager.state.settings;\n if (settings[\"🔍MagnifyGlass.ShowHoveringControls\"] === false) {\n controlsElement.style.display = 'none';\n return;\n }\n\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const magnifyGlass = window.comfyUIMagnifyGlass;\n let referenceRect: DOMRect | null = null;\n\n // 1. Position relative to PANEL (Preferred if visible)\n if (isPanelVisible && this.panelElement) {\n // Priority: Use Cached Position (Logical) -> Avoids 'chasing' CSS transitions\n if (this.cachedPanelPosition) {\n // Convert simple object to Rect-like interface for compatibility\n referenceRect = {\n left: this.cachedPanelPosition.x,\n top: this.cachedPanelPosition.y,\n right: this.cachedPanelPosition.x + this.cachedPanelPosition.width,\n bottom: this.cachedPanelPosition.y + this.cachedPanelPosition.height,\n width: this.cachedPanelPosition.width,\n height: this.cachedPanelPosition.height\n } as DOMRect;\n } else {\n // Fallback: Read DOM (May cause layout thrashing/lag)\n referenceRect = this.panelElement.getBoundingClientRect();\n }\n }\n // 2. Position relative to GLASS (Fallback if panel hidden)\n else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) {\n referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect();\n // Don't show controls if glass is hidden/invalid\n if (referenceRect.width === 0 || referenceRect.height === 0) {\n controlsElement.style.display = 'none';\n return;\n }\n }\n\n if (!referenceRect) return;\n\n // Enforce no transition on controls to prevent 'rubber-banding'\n controlsElement.style.transition = 'none';\n\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"right\";\n const margin = 4;\n\n let left: number;\n let top: number;\n\n switch (controlsPosition) {\n case \"left\":\n left = referenceRect.left - controlsElement.offsetWidth - margin;\n top = referenceRect.top;\n break;\n case \"right\":\n left = referenceRect.right + margin;\n top = referenceRect.top;\n break;\n case \"top\":\n left = referenceRect.left;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = referenceRect.left;\n top = referenceRect.bottom + margin;\n break;\n case \"top-right\":\n left = referenceRect.right - controlsElement.offsetWidth;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = referenceRect.right + margin;\n top = referenceRect.top;\n }\n\n // Keep controls within viewport\n left = Math.max(10, Math.min(left, window.innerWidth - controlsElement.offsetWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));\n\n controlsElement.style.left = `${left}px`;\n controlsElement.style.top = `${top}px`;\n\n // Show controls now that they're positioned (initially hidden off-screen)\n controlsElement.style.display = 'flex';\n controlsElement.style.visibility = 'visible';\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file