diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..d37a631 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-23 - Throttling High-Frequency Events +**Learning:** High-frequency events like `mousemove` can cause performance issues if they trigger expensive DOM updates or calculations on every firing. +**Action:** Use `requestAnimationFrame` to throttle these updates to the screen refresh rate. Ensure that the event data (like coordinates) is captured synchronously in the event handler so that the rAF callback has access to the latest state. diff --git a/src/magnify-glass/EventHandler.ts b/src/magnify-glass/EventHandler.ts index 8485b70..f25a4c8 100644 --- a/src/magnify-glass/EventHandler.ts +++ b/src/magnify-glass/EventHandler.ts @@ -16,6 +16,8 @@ import type { MagnifyGlass } from './MagnifyGlass'; export class EventHandler { magnifyGlass: MagnifyGlass; + private rafId: number | null = null; + constructor(magnifyGlass: MagnifyGlass) { this.magnifyGlass = magnifyGlass; @@ -43,6 +45,11 @@ export class EventHandler { document.removeEventListener("keyup", this.handleKeyUp); document.removeEventListener("mousemove", this.handleMouseMove); window.removeEventListener("resize", this.handleResize); + + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } } /** @@ -222,26 +229,39 @@ export class EventHandler { if (!this.magnifyGlass.state.active || !this.magnifyGlass.litegraphCanvas) return; - const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect(); - const cssMouseXOnCanvas = e.clientX - rect.left; - const cssMouseYOnCanvas = e.clientY - rect.top; + // Throttle updates using requestAnimationFrame to prevent excessive calculations and layout thrashing + if (this.rafId === null) { + this.rafId = requestAnimationFrame(() => { + this.rafId = null; - // Check if the cursor is over the canvas element - if (cssMouseXOnCanvas >= 0 && cssMouseXOnCanvas <= rect.width && - cssMouseYOnCanvas >= 0 && cssMouseYOnCanvas <= rect.height) { + // Re-check active state in case it changed while waiting for frame + if (!this.magnifyGlass.state.active || !this.magnifyGlass.litegraphCanvas) return; - const canvasElement = this.magnifyGlass.litegraphCanvas; - const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1; - const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1; + const clientX = this.magnifyGlass.lastKnownMousePosition.x; + const clientY = this.magnifyGlass.lastKnownMousePosition.y; - const pixelX = cssMouseXOnCanvas * scaleX; - const pixelY = cssMouseYOnCanvas * scaleY; + const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect(); + const cssMouseXOnCanvas = clientX - rect.left; + const cssMouseYOnCanvas = clientY - rect.top; - this.magnifyGlass.state.x = pixelX; - this.magnifyGlass.state.y = pixelY; + // Check if the cursor is over the canvas element + if (cssMouseXOnCanvas >= 0 && cssMouseXOnCanvas <= rect.width && + cssMouseYOnCanvas >= 0 && cssMouseYOnCanvas <= rect.height) { - this.magnifyGlass.ui.positionGlass(e.clientX, e.clientY); - this.magnifyGlass.updateMagnifiedView(); + const canvasElement = this.magnifyGlass.litegraphCanvas; + const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1; + const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1; + + const pixelX = cssMouseXOnCanvas * scaleX; + const pixelY = cssMouseYOnCanvas * scaleY; + + this.magnifyGlass.state.x = pixelX; + this.magnifyGlass.state.y = pixelY; + + this.magnifyGlass.ui.positionGlass(clientX, clientY); + this.magnifyGlass.updateMagnifiedView(); + } + }); } }