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:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user