Merge pull request #25 from AEmotionStudio/bolt/optimize-render-overlays-9313858051447850514

⚡ Bolt: Optimize renderHtmlOverlays loop
This commit is contained in:
Æmotion Studio
2026-01-17 22:15:16 -08:00
committed by GitHub
2 changed files with 18 additions and 19 deletions
+7 -6
View File
@@ -1,6 +1,7 @@
## 2024-05-23 - Layout Thrashing in Render Loops
**Learning:** Mixing DOM reads (like `getBoundingClientRect` or `getComputedStyle`) with DOM writes (like `appendChild` or `innerHTML = ''`) inside a loop causes "Layout Thrashing", forcing the browser to synchronously recalculate layout on every iteration. This is a massive performance bottleneck.
**Action:** Always separate read and write phases.
1. **Read Phase:** Collect all necessary measurements (rects, styles) and store them in a temporary structure (e.g., `RenderTask[]`).
2. **Write Phase:** Perform all DOM mutations in one go, ideally using a `DocumentFragment` to batch appends before inserting into the live DOM.
**Critical:** Ensure logic that depends on element type (like detecting videos to keep the animation loop alive) is preserved in the Read Phase.
## 2024-05-23 - Loop Invariant Code Motion
**Learning:** Even simple arithmetic operations (`/`, `+`, `*`) and property accesses (`state.canvasScale`, `rect.width`) add up when performed inside nested loops running at 60 FPS (e.g., iterating over nodes and then widgets).
**Action:** Always check if variables calculated inside a loop depend on the loop iterator. If not, hoist them to the outer scope ("Redundant calculations in loops").
## 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.
+11 -13
View File
@@ -412,8 +412,17 @@ export class MagnifyGlass {
const rect = this.litegraphCanvas.getBoundingClientRect();
const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1;
const cursorCssX = this.state.x / dpr;
const cursorCssY = this.state.y / dpr;
// Pre-calculate invariant values for the frame
const currentScale = this.state.canvasScale;
const isVirtualZoomMode = currentScale < 0.7;
const canvasRect = rect;
// Pivot point (mouse position) in CSS coordinates
const pivotCssX = this.state.x / dpr;
const pivotCssY = this.state.y / dpr;
const cursorCssX = pivotCssX;
const cursorCssY = pivotCssY;
const cursorGraphX = (cursorCssX - this.state.canvasOffsetX) / this.state.canvasScale;
const cursorGraphY = (cursorCssY - this.state.canvasOffsetY) / this.state.canvasScale;
const targetGraphCenterX = cursorGraphX + this.config.offsetX;
@@ -506,23 +515,12 @@ export class MagnifyGlass {
// READ: Force layout calculation here, batched together
const widgetRect = elementToProcess.getBoundingClientRect();
// Optimization: Use the widgetRect to check overlap immediately before potentially expensive computations
// Reuse canvasRect and dpr from top level scope
const canvasRect = rect;
const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1;
const currentScale = this.state.canvasScale;
const isVirtualZoomMode = currentScale < 0.7;
// Widget position in CSS coordinates relative to canvas
const widgetCssX = widgetRect.left - canvasRect.left;
const widgetCssY = widgetRect.top - canvasRect.top;
const widgetCssWidth = widgetRect.width;
const widgetCssHeight = widgetRect.height;
// Pivot point (mouse position) in CSS coordinates
const pivotCssX = this.state.x / dpr;
const pivotCssY = this.state.y / dpr;
let finalWidgetCssX: number;
let finalWidgetCssY: number;
let finalWidgetCssWidth: number;