From 98cb58ef592001e873f9a0de102987f1788e9771 Mon Sep 17 00:00:00 2001 From: Arshaan Date: Wed, 12 Nov 2025 01:18:28 +0330 Subject: [PATCH] Refactor event handling in LiquidEther component for improved hover state management --- .../Backgrounds/LiquidEther/LiquidEther.jsx | 95 ++++++++++++------- .../Backgrounds/LiquidEther/LiquidEther.jsx | 95 ++++++++++++------- .../Backgrounds/LiquidEther/LiquidEther.tsx | 83 ++++++++++------ .../Backgrounds/LiquidEther/LiquidEther.tsx | 83 ++++++++++------ 4 files changed, 222 insertions(+), 134 deletions(-) diff --git a/src/content/Backgrounds/LiquidEther/LiquidEther.jsx b/src/content/Backgrounds/LiquidEther/LiquidEther.jsx index 83798f9b..393ca31f 100644 --- a/src/content/Backgrounds/LiquidEther/LiquidEther.jsx +++ b/src/content/Backgrounds/LiquidEther/LiquidEther.jsx @@ -121,12 +121,8 @@ export default function LiquidEther({ this.diff = new THREE.Vector2(); this.timer = null; this.container = null; - this._onMouseMove = this.onDocumentMouseMove.bind(this); - this._onTouchStart = this.onDocumentTouchStart.bind(this); - this._onTouchMove = this.onDocumentTouchMove.bind(this); - this._onMouseEnter = this.onMouseEnter.bind(this); - this._onMouseLeave = this.onMouseLeave.bind(this); - this._onTouchEnd = this.onTouchEnd.bind(this); + this.docTarget = null; + this.listenerTarget = null; this.isHoverInside = false; this.hasUserControl = false; this.isAutoActive = false; @@ -137,34 +133,61 @@ export default function LiquidEther({ this.takeoverFrom = new THREE.Vector2(); this.takeoverTo = new THREE.Vector2(); this.onInteract = null; + this._onMouseMove = this.onDocumentMouseMove.bind(this); + this._onTouchStart = this.onDocumentTouchStart.bind(this); + this._onTouchMove = this.onDocumentTouchMove.bind(this); + this._onTouchEnd = this.onTouchEnd.bind(this); + this._onDocumentLeave = this.onDocumentLeave.bind(this); } init(container) { this.container = container; - container.addEventListener('mousemove', this._onMouseMove, false); - container.addEventListener('touchstart', this._onTouchStart, false); - container.addEventListener('touchmove', this._onTouchMove, false); - container.addEventListener('mouseenter', this._onMouseEnter, false); - container.addEventListener('mouseleave', this._onMouseLeave, false); - container.addEventListener('touchend', this._onTouchEnd, false); + this.docTarget = container.ownerDocument || null; + const defaultView = + (this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null); + if (!defaultView) return; + this.listenerTarget = defaultView; + this.listenerTarget.addEventListener('mousemove', this._onMouseMove); + this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true }); + this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true }); + this.listenerTarget.addEventListener('touchend', this._onTouchEnd); + if (this.docTarget) { + this.docTarget.addEventListener('mouseleave', this._onDocumentLeave); + } } dispose() { - if (!this.container) return; - this.container.removeEventListener('mousemove', this._onMouseMove, false); - this.container.removeEventListener('touchstart', this._onTouchStart, false); - this.container.removeEventListener('touchmove', this._onTouchMove, false); - this.container.removeEventListener('mouseenter', this._onMouseEnter, false); - this.container.removeEventListener('mouseleave', this._onMouseLeave, false); - this.container.removeEventListener('touchend', this._onTouchEnd, false); + if (this.listenerTarget) { + this.listenerTarget.removeEventListener('mousemove', this._onMouseMove); + this.listenerTarget.removeEventListener('touchstart', this._onTouchStart); + this.listenerTarget.removeEventListener('touchmove', this._onTouchMove); + this.listenerTarget.removeEventListener('touchend', this._onTouchEnd); + } + if (this.docTarget) { + this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave); + } + this.listenerTarget = null; + this.docTarget = null; + this.container = null; + } + isPointInside(clientX, clientY) { + if (!this.container) return false; + const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return false; + return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom; + } + updateHoverState(clientX, clientY) { + this.isHoverInside = this.isPointInside(clientX, clientY); + return this.isHoverInside; } setCoords(x, y) { if (!this.container) return; - if (this.timer) clearTimeout(this.timer); + if (this.timer) window.clearTimeout(this.timer); const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; const nx = (x - rect.left) / rect.width; const ny = (y - rect.top) / rect.height; this.coords.set(nx * 2 - 1, -(ny * 2 - 1)); this.mouseMoved = true; - this.timer = setTimeout(() => { + this.timer = window.setTimeout(() => { this.mouseMoved = false; }, 100); } @@ -173,9 +196,12 @@ export default function LiquidEther({ this.mouseMoved = true; } onDocumentMouseMove(event) { + if (!this.updateHoverState(event.clientX, event.clientY)) return; if (this.onInteract) this.onInteract(); if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) { + if (!this.container) return; const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; const nx = (event.clientX - rect.left) / rect.width; const ny = (event.clientY - rect.top) / rect.height; this.takeoverFrom.copy(this.coords); @@ -190,27 +216,24 @@ export default function LiquidEther({ this.hasUserControl = true; } onDocumentTouchStart(event) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - this.hasUserControl = true; - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); + this.hasUserControl = true; } onDocumentTouchMove(event) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); } onTouchEnd() { this.isHoverInside = false; } - onMouseEnter() { - this.isHoverInside = true; - } - onMouseLeave() { + onDocumentLeave() { this.isHoverInside = false; } update() { diff --git a/src/tailwind/Backgrounds/LiquidEther/LiquidEther.jsx b/src/tailwind/Backgrounds/LiquidEther/LiquidEther.jsx index 3ec755ca..2f40b438 100644 --- a/src/tailwind/Backgrounds/LiquidEther/LiquidEther.jsx +++ b/src/tailwind/Backgrounds/LiquidEther/LiquidEther.jsx @@ -120,12 +120,8 @@ export default function LiquidEther({ this.diff = new THREE.Vector2(); this.timer = null; this.container = null; - this._onMouseMove = this.onDocumentMouseMove.bind(this); - this._onTouchStart = this.onDocumentTouchStart.bind(this); - this._onTouchMove = this.onDocumentTouchMove.bind(this); - this._onMouseEnter = this.onMouseEnter.bind(this); - this._onMouseLeave = this.onMouseLeave.bind(this); - this._onTouchEnd = this.onTouchEnd.bind(this); + this.docTarget = null; + this.listenerTarget = null; this.isHoverInside = false; this.hasUserControl = false; this.isAutoActive = false; @@ -136,34 +132,61 @@ export default function LiquidEther({ this.takeoverFrom = new THREE.Vector2(); this.takeoverTo = new THREE.Vector2(); this.onInteract = null; + this._onMouseMove = this.onDocumentMouseMove.bind(this); + this._onTouchStart = this.onDocumentTouchStart.bind(this); + this._onTouchMove = this.onDocumentTouchMove.bind(this); + this._onTouchEnd = this.onTouchEnd.bind(this); + this._onDocumentLeave = this.onDocumentLeave.bind(this); } init(container) { this.container = container; - container.addEventListener('mousemove', this._onMouseMove, false); - container.addEventListener('touchstart', this._onTouchStart, false); - container.addEventListener('touchmove', this._onTouchMove, false); - container.addEventListener('mouseenter', this._onMouseEnter, false); - container.addEventListener('mouseleave', this._onMouseLeave, false); - container.addEventListener('touchend', this._onTouchEnd, false); + this.docTarget = container.ownerDocument || null; + const defaultView = + (this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null); + if (!defaultView) return; + this.listenerTarget = defaultView; + this.listenerTarget.addEventListener('mousemove', this._onMouseMove); + this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true }); + this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true }); + this.listenerTarget.addEventListener('touchend', this._onTouchEnd); + if (this.docTarget) { + this.docTarget.addEventListener('mouseleave', this._onDocumentLeave); + } } dispose() { - if (!this.container) return; - this.container.removeEventListener('mousemove', this._onMouseMove, false); - this.container.removeEventListener('touchstart', this._onTouchStart, false); - this.container.removeEventListener('touchmove', this._onTouchMove, false); - this.container.removeEventListener('mouseenter', this._onMouseEnter, false); - this.container.removeEventListener('mouseleave', this._onMouseLeave, false); - this.container.removeEventListener('touchend', this._onTouchEnd, false); + if (this.listenerTarget) { + this.listenerTarget.removeEventListener('mousemove', this._onMouseMove); + this.listenerTarget.removeEventListener('touchstart', this._onTouchStart); + this.listenerTarget.removeEventListener('touchmove', this._onTouchMove); + this.listenerTarget.removeEventListener('touchend', this._onTouchEnd); + } + if (this.docTarget) { + this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave); + } + this.listenerTarget = null; + this.docTarget = null; + this.container = null; + } + isPointInside(clientX, clientY) { + if (!this.container) return false; + const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return false; + return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom; + } + updateHoverState(clientX, clientY) { + this.isHoverInside = this.isPointInside(clientX, clientY); + return this.isHoverInside; } setCoords(x, y) { if (!this.container) return; - if (this.timer) clearTimeout(this.timer); + if (this.timer) window.clearTimeout(this.timer); const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; const nx = (x - rect.left) / rect.width; const ny = (y - rect.top) / rect.height; this.coords.set(nx * 2 - 1, -(ny * 2 - 1)); this.mouseMoved = true; - this.timer = setTimeout(() => { + this.timer = window.setTimeout(() => { this.mouseMoved = false; }, 100); } @@ -172,9 +195,12 @@ export default function LiquidEther({ this.mouseMoved = true; } onDocumentMouseMove(event) { + if (!this.updateHoverState(event.clientX, event.clientY)) return; if (this.onInteract) this.onInteract(); if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) { + if (!this.container) return; const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; const nx = (event.clientX - rect.left) / rect.width; const ny = (event.clientY - rect.top) / rect.height; this.takeoverFrom.copy(this.coords); @@ -189,27 +215,24 @@ export default function LiquidEther({ this.hasUserControl = true; } onDocumentTouchStart(event) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - this.hasUserControl = true; - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); + this.hasUserControl = true; } onDocumentTouchMove(event) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); } onTouchEnd() { this.isHoverInside = false; } - onMouseEnter() { - this.isHoverInside = true; - } - onMouseLeave() { + onDocumentLeave() { this.isHoverInside = false; } update() { diff --git a/src/ts-default/Backgrounds/LiquidEther/LiquidEther.tsx b/src/ts-default/Backgrounds/LiquidEther/LiquidEther.tsx index 3ea976f8..444002dd 100644 --- a/src/ts-default/Backgrounds/LiquidEther/LiquidEther.tsx +++ b/src/ts-default/Backgrounds/LiquidEther/LiquidEther.tsx @@ -171,6 +171,8 @@ export default function LiquidEther({ diff = new THREE.Vector2(); timer: number | null = null; container: HTMLElement | null = null; + docTarget: Document | null = null; + listenerTarget: Window | null = null; isHoverInside = false; hasUserControl = false; isAutoActive = false; @@ -184,32 +186,53 @@ export default function LiquidEther({ private _onMouseMove = this.onDocumentMouseMove.bind(this); private _onTouchStart = this.onDocumentTouchStart.bind(this); private _onTouchMove = this.onDocumentTouchMove.bind(this); - private _onMouseEnter = this.onMouseEnter.bind(this); - private _onMouseLeave = this.onMouseLeave.bind(this); private _onTouchEnd = this.onTouchEnd.bind(this); + private _onDocumentLeave = this.onDocumentLeave.bind(this); init(container: HTMLElement) { this.container = container; - container.addEventListener('mousemove', this._onMouseMove); - container.addEventListener('touchstart', this._onTouchStart, { passive: true }); - container.addEventListener('touchmove', this._onTouchMove, { passive: true }); - container.addEventListener('mouseenter', this._onMouseEnter); - container.addEventListener('mouseleave', this._onMouseLeave); - container.addEventListener('touchend', this._onTouchEnd); + this.docTarget = container.ownerDocument || null; + const defaultView = this.docTarget?.defaultView || (typeof window !== 'undefined' ? window : null); + if (!defaultView) return; + this.listenerTarget = defaultView; + this.listenerTarget.addEventListener('mousemove', this._onMouseMove); + this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { + passive: true + }); + this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { + passive: true + }); + this.listenerTarget.addEventListener('touchend', this._onTouchEnd); + this.docTarget?.addEventListener('mouseleave', this._onDocumentLeave); } dispose() { - const c = this.container; - if (!c) return; - c.removeEventListener('mousemove', this._onMouseMove); - c.removeEventListener('touchstart', this._onTouchStart); - c.removeEventListener('touchmove', this._onTouchMove); - c.removeEventListener('mouseenter', this._onMouseEnter); - c.removeEventListener('mouseleave', this._onMouseLeave); - c.removeEventListener('touchend', this._onTouchEnd); + if (this.listenerTarget) { + this.listenerTarget.removeEventListener('mousemove', this._onMouseMove); + this.listenerTarget.removeEventListener('touchstart', this._onTouchStart); + this.listenerTarget.removeEventListener('touchmove', this._onTouchMove); + this.listenerTarget.removeEventListener('touchend', this._onTouchEnd); + } + if (this.docTarget) { + this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave); + } + this.listenerTarget = null; + this.docTarget = null; + this.container = null; + } + private isPointInside(clientX: number, clientY: number) { + if (!this.container) return false; + const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return false; + return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom; + } + private updateHoverState(clientX: number, clientY: number) { + this.isHoverInside = this.isPointInside(clientX, clientY); + return this.isHoverInside; } setCoords(x: number, y: number) { if (!this.container) return; if (this.timer) window.clearTimeout(this.timer); const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; const nx = (x - rect.left) / rect.width; const ny = (y - rect.top) / rect.height; this.coords.set(nx * 2 - 1, -(ny * 2 - 1)); @@ -223,6 +246,7 @@ export default function LiquidEther({ this.mouseMoved = true; } onDocumentMouseMove(event: MouseEvent) { + if (!this.updateHoverState(event.clientX, event.clientY)) return; if (this.onInteract) this.onInteract(); if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) { if (!this.container) return; @@ -241,27 +265,24 @@ export default function LiquidEther({ this.hasUserControl = true; } onDocumentTouchStart(event: TouchEvent) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - this.hasUserControl = true; - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); + this.hasUserControl = true; } onDocumentTouchMove(event: TouchEvent) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); } onTouchEnd() { this.isHoverInside = false; } - onMouseEnter() { - this.isHoverInside = true; - } - onMouseLeave() { + onDocumentLeave() { this.isHoverInside = false; } update() { diff --git a/src/ts-tailwind/Backgrounds/LiquidEther/LiquidEther.tsx b/src/ts-tailwind/Backgrounds/LiquidEther/LiquidEther.tsx index 7c6e14a8..fa9ab18d 100644 --- a/src/ts-tailwind/Backgrounds/LiquidEther/LiquidEther.tsx +++ b/src/ts-tailwind/Backgrounds/LiquidEther/LiquidEther.tsx @@ -170,6 +170,8 @@ export default function LiquidEther({ diff = new THREE.Vector2(); timer: number | null = null; container: HTMLElement | null = null; + docTarget: Document | null = null; + listenerTarget: Window | null = null; isHoverInside = false; hasUserControl = false; isAutoActive = false; @@ -183,32 +185,53 @@ export default function LiquidEther({ private _onMouseMove = this.onDocumentMouseMove.bind(this); private _onTouchStart = this.onDocumentTouchStart.bind(this); private _onTouchMove = this.onDocumentTouchMove.bind(this); - private _onMouseEnter = this.onMouseEnter.bind(this); - private _onMouseLeave = this.onMouseLeave.bind(this); private _onTouchEnd = this.onTouchEnd.bind(this); + private _onDocumentLeave = this.onDocumentLeave.bind(this); init(container: HTMLElement) { this.container = container; - container.addEventListener('mousemove', this._onMouseMove); - container.addEventListener('touchstart', this._onTouchStart, { passive: true }); - container.addEventListener('touchmove', this._onTouchMove, { passive: true }); - container.addEventListener('mouseenter', this._onMouseEnter); - container.addEventListener('mouseleave', this._onMouseLeave); - container.addEventListener('touchend', this._onTouchEnd); + this.docTarget = container.ownerDocument || null; + const defaultView = this.docTarget?.defaultView || (typeof window !== 'undefined' ? window : null); + if (!defaultView) return; + this.listenerTarget = defaultView; + this.listenerTarget.addEventListener('mousemove', this._onMouseMove); + this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { + passive: true + }); + this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { + passive: true + }); + this.listenerTarget.addEventListener('touchend', this._onTouchEnd); + this.docTarget?.addEventListener('mouseleave', this._onDocumentLeave); } dispose() { - const c = this.container; - if (!c) return; - c.removeEventListener('mousemove', this._onMouseMove); - c.removeEventListener('touchstart', this._onTouchStart); - c.removeEventListener('touchmove', this._onTouchMove); - c.removeEventListener('mouseenter', this._onMouseEnter); - c.removeEventListener('mouseleave', this._onMouseLeave); - c.removeEventListener('touchend', this._onTouchEnd); + if (this.listenerTarget) { + this.listenerTarget.removeEventListener('mousemove', this._onMouseMove); + this.listenerTarget.removeEventListener('touchstart', this._onTouchStart); + this.listenerTarget.removeEventListener('touchmove', this._onTouchMove); + this.listenerTarget.removeEventListener('touchend', this._onTouchEnd); + } + if (this.docTarget) { + this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave); + } + this.listenerTarget = null; + this.docTarget = null; + this.container = null; + } + private isPointInside(clientX: number, clientY: number) { + if (!this.container) return false; + const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return false; + return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom; + } + private updateHoverState(clientX: number, clientY: number) { + this.isHoverInside = this.isPointInside(clientX, clientY); + return this.isHoverInside; } setCoords(x: number, y: number) { if (!this.container) return; if (this.timer) window.clearTimeout(this.timer); const rect = this.container.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; const nx = (x - rect.left) / rect.width; const ny = (y - rect.top) / rect.height; this.coords.set(nx * 2 - 1, -(ny * 2 - 1)); @@ -222,6 +245,7 @@ export default function LiquidEther({ this.mouseMoved = true; } onDocumentMouseMove(event: MouseEvent) { + if (!this.updateHoverState(event.clientX, event.clientY)) return; if (this.onInteract) this.onInteract(); if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) { if (!this.container) return; @@ -240,27 +264,24 @@ export default function LiquidEther({ this.hasUserControl = true; } onDocumentTouchStart(event: TouchEvent) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - this.hasUserControl = true; - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); + this.hasUserControl = true; } onDocumentTouchMove(event: TouchEvent) { - if (event.touches.length === 1) { - const t = event.touches[0]; - if (this.onInteract) this.onInteract(); - this.setCoords(t.pageX, t.pageY); - } + if (event.touches.length !== 1) return; + const t = event.touches[0]; + if (!this.updateHoverState(t.clientX, t.clientY)) return; + if (this.onInteract) this.onInteract(); + this.setCoords(t.clientX, t.clientY); } onTouchEnd() { this.isHoverInside = false; } - onMouseEnter() { - this.isHoverInside = true; - } - onMouseLeave() { + onDocumentLeave() { this.isHoverInside = false; } update() {