diff --git a/.jules/bolt.md b/.jules/bolt.md index 96282e3..4a0a090 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,7 @@ ## 2024-05-23 - DOM Property Access in Loops **Learning:** Accessing DOM properties like `canvas.width` or `element.style` inside a loop is much slower than accessing a local variable. **Action:** Cache DOM properties into local variables before entering hot loops. + +## 2024-05-23 - Read-Write-Read Layout Thrashing +**Learning:** Writing to the DOM (e.g., `element.style.top = ...`) immediately invalidates the layout. If you subsequently read a layout property (e.g., `getBoundingClientRect()`) in the same frame, the browser must force a synchronous layout recalculation. +**Action:** In event handlers that move elements, Read all necessary dimensions first, then Perform all Writes. If a downstream method (like `updateMagnifiedView`) needs dimensions, pass the cached values instead of re-reading them. diff --git a/src/magnify-glass/EventHandler.ts b/src/magnify-glass/EventHandler.ts index f25a4c8..23624dc 100644 --- a/src/magnify-glass/EventHandler.ts +++ b/src/magnify-glass/EventHandler.ts @@ -259,7 +259,9 @@ export class EventHandler { this.magnifyGlass.state.y = pixelY; this.magnifyGlass.ui.positionGlass(clientX, clientY); - this.magnifyGlass.updateMagnifiedView(); + // Optimization: Pass the cached rect to avoid re-measuring in calculateSourceRegion + // which would cause layout thrashing (Read-Write-Read) + this.magnifyGlass.updateMagnifiedView(rect); } }); } @@ -305,7 +307,8 @@ export class EventHandler { this.magnifyGlass.ui.positionGlass(clientX, clientY); } - this.magnifyGlass.updateMagnifiedView(); + // Optimization: Pass the cached rect + this.magnifyGlass.updateMagnifiedView(rect); } } } diff --git a/src/magnify-glass/MagnifyGlass.ts b/src/magnify-glass/MagnifyGlass.ts index 56aff2b..09a0955 100644 --- a/src/magnify-glass/MagnifyGlass.ts +++ b/src/magnify-glass/MagnifyGlass.ts @@ -239,8 +239,9 @@ export class MagnifyGlass { /** * Update the magnified view. + * @param rect - Optional pre-calculated bounding rect of the canvas to avoid layout thrashing */ - updateMagnifiedView(): void { + updateMagnifiedView(rect?: DOMRect): void { if (!this.state.active || !this.renderer || !this.litegraphCanvas) { return; } @@ -255,7 +256,7 @@ export class MagnifyGlass { this.updateCanvasTransformation(); // Calculate the source region - this.calculateSourceRegion(); + this.calculateSourceRegion(rect); // Schedule the rendering operation if (!this.state.isRenderScheduled) { @@ -321,8 +322,9 @@ export class MagnifyGlass { /** * Calculate the source region for magnification. + * @param cachedRect - Optional pre-calculated bounding rect of the canvas to avoid layout thrashing */ - calculateSourceRegion(): void { + calculateSourceRegion(cachedRect?: DOMRect): void { const cursorPixelX = this.state.x; const cursorPixelY = this.state.y; const canvasScale = this.state.canvasScale; @@ -333,7 +335,8 @@ export class MagnifyGlass { if (!this.litegraphCanvas) return; // Get DPR (Device Pixel Ratio) - relationship between Backing Store pixels and CSS pixels - const rect = this.litegraphCanvas.getBoundingClientRect(); + // Optimization: Use cached rect if provided to avoid forcing a reflow (Read-Write-Read cycle) + const rect = cachedRect || this.litegraphCanvas.getBoundingClientRect(); const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1; // Convert cursor Backing pixels to CSS pixels (Screen pixels)