perf: Cache panel position to reduce layout thrashing and synchronize control movement by disabling transitions.

This commit is contained in:
AEmotionStudio
2026-01-02 15:16:27 -08:00
parent 3a8ff12ad1
commit 5a53d12bc3
3 changed files with 48 additions and 6 deletions
+29 -4
View File
@@ -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;
+18 -1
View File
@@ -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;
File diff suppressed because one or more lines are too long