perf: prevent layout thrashing in magnifier update loop

Refactors `MagnifyGlass.updateMagnifiedView` and `calculateSourceRegion` to accept an optional cached `DOMRect`. This allows `EventHandler` to pass the canvas dimensions it read before updating the glass position, preventing a forced synchronous reflow (Read-Write-Read cycle) that was previously occurring on every mouse move frame.

Optimizes the hot path for mouse movement and magnifier updates.
This commit is contained in:
google-labs-jules[bot]
2026-01-19 05:00:41 +00:00
parent acc96254a6
commit bb9c545eda
3 changed files with 16 additions and 6 deletions
+4
View File
@@ -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.
+5 -2
View File
@@ -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);
}
}
}
+7 -4
View File
@@ -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)