From 6b17b8b007e379f02ff1247dd24c3a2881faeaab Mon Sep 17 00:00:00 2001 From: Melvin Ragusa Date: Sat, 24 Jan 2026 11:33:46 +0100 Subject: [PATCH] perf: optimized gradientblinds --- .../effects/GradientBlinds.test.tsx | 64 +++++++++++++++---- src/components/effects/GradientBlinds.tsx | 3 +- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/components/effects/GradientBlinds.test.tsx b/src/components/effects/GradientBlinds.test.tsx index d75d181..fdb4c45 100644 --- a/src/components/effects/GradientBlinds.test.tsx +++ b/src/components/effects/GradientBlinds.test.tsx @@ -13,34 +13,37 @@ vi.mock('ogl', () => { drawingBufferWidth: 100, drawingBufferHeight: 100, }; - setSize() {} - render() {} + setSize() { } + render() { } }, Program: class { - remove() {} + remove() { } }, Mesh: class { - remove() {} + remove() { } }, Geometry: class { - remove() {} + remove() { } }, Triangle: class { - remove() {} + remove() { } }, + }; }); // Mock ResizeObserver globalThis.ResizeObserver = class ResizeObserver { - observe() {} - unobserve() {} - disconnect() {} + observe() { } + unobserve() { } + disconnect() { } }; describe('GradientBlinds', () => { let rafSpy: any; let cancelRafSpy: any; + let addEventListenerSpy: any; + let removeEventListenerSpy: any; let ioCallback: (entries: IntersectionObserverEntry[]) => void; let observeSpy: any; let disconnectSpy: any; @@ -51,6 +54,8 @@ describe('GradientBlinds', () => { }); cancelRafSpy = vi.spyOn(window, 'cancelAnimationFrame').mockImplementation((_id: number) => { }); + addEventListenerSpy = vi.spyOn(window, 'addEventListener'); + removeEventListenerSpy = vi.spyOn(window, 'removeEventListener'); // Mock IntersectionObserver observeSpy = vi.fn(); @@ -133,6 +138,44 @@ describe('GradientBlinds', () => { expect(disconnectSpy).toHaveBeenCalled(); }); + it('toggles pointer event listener based on visibility', () => { + const { unmount } = render(); + + // Should not listen initially (not visible) + // Initially not called + expect(addEventListenerSpy).not.toHaveBeenCalledWith('pointermove', expect.any(Function)); + + // Simulate on-screen + act(() => { + if (ioCallback) { + ioCallback([{ isIntersecting: true } as IntersectionObserverEntry]); + } + }); + expect(addEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function)); + + // Reset + addEventListenerSpy.mockClear(); + removeEventListenerSpy.mockClear(); + + // Simulate off-screen + act(() => { + if (ioCallback) { + ioCallback([{ isIntersecting: false } as IntersectionObserverEntry]); + } + }); + expect(removeEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function)); + + // Simulate on-screen again + act(() => { + if (ioCallback) { + ioCallback([{ isIntersecting: true } as IntersectionObserverEntry]); + } + }); + expect(addEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function)); + + unmount(); + expect(removeEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function)); + }); it('minimizes getBoundingClientRect calls during pointer move', () => { const { unmount } = render(); @@ -155,8 +198,7 @@ describe('GradientBlinds', () => { window.dispatchEvent(event); }); - // EXPECTATION: It should NOT be called. - // This will fail currently. + // EXPECTATION: It should NOT be called because the listener shouldn't be attached (not visible) expect(spy).not.toHaveBeenCalled(); unmount(); diff --git a/src/components/effects/GradientBlinds.tsx b/src/components/effects/GradientBlinds.tsx index 9b44f1f..48c0a71 100644 --- a/src/components/effects/GradientBlinds.tsx +++ b/src/components/effects/GradientBlinds.tsx @@ -330,7 +330,6 @@ void main() { uniforms.iMouse.value = [x, y]; } }; - window.addEventListener('pointermove', onPointerMove); const loop = (t: number) => { rafRef.current = requestAnimationFrame(loop); @@ -364,11 +363,13 @@ void main() { lastTimeRef.current = 0; rafRef.current = requestAnimationFrame(loop); } + window.addEventListener('pointermove', onPointerMove); } else { if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } + window.removeEventListener('pointermove', onPointerMove); } }); observer.observe(container);