From e36d9382827c3b7ca302cb640fd99654496e2a87 Mon Sep 17 00:00:00 2001 From: Lin Junrong Date: Fri, 31 Jul 2026 17:53:09 +0800 Subject: [PATCH] fix(Lightning, RippleGrid): cancel the render loop on cleanup Both components run a self-scheduling requestAnimationFrame loop and never cancel it. The cleanup removes listeners (and RippleGrid even drops the WebGL context and the canvas) but the loop keeps calling into GL every frame after unmount, holding the context, program and closure state alive. Lightning is worse: its effect depends on [hue, xOffset, speed, intensity, size], so every prop change starts another loop without stopping the previous one. Captures the frame id and cancels it in the cleanup. All four variants. --- src/content/Backgrounds/Lightning/Lightning.jsx | 6 ++++-- src/content/Backgrounds/RippleGrid/RippleGrid.jsx | 6 ++++-- src/tailwind/Backgrounds/Lightning/Lightning.jsx | 6 ++++-- src/tailwind/Backgrounds/RippleGrid/RippleGrid.jsx | 6 ++++-- src/ts-default/Backgrounds/Lightning/Lightning.tsx | 6 ++++-- src/ts-default/Backgrounds/RippleGrid/RippleGrid.tsx | 6 ++++-- src/ts-tailwind/Backgrounds/Lightning/Lightning.tsx | 6 ++++-- src/ts-tailwind/Backgrounds/RippleGrid/RippleGrid.tsx | 6 ++++-- 8 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/content/Backgrounds/Lightning/Lightning.jsx b/src/content/Backgrounds/Lightning/Lightning.jsx index d8be403b8..2a3dbe389 100644 --- a/src/content/Backgrounds/Lightning/Lightning.jsx +++ b/src/content/Backgrounds/Lightning/Lightning.jsx @@ -154,6 +154,7 @@ const Lightning = ({ hue = 230, xOffset = 0, speed = 1, intensity = 1, size = 1 const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity'); const uSizeLocation = gl.getUniformLocation(program, 'uSize'); + let animationFrameId; const startTime = performance.now(); const render = () => { resizeCanvas(); @@ -167,11 +168,12 @@ const Lightning = ({ hue = 230, xOffset = 0, speed = 1, intensity = 1, size = 1 gl.uniform1f(uIntensityLocation, intensity); gl.uniform1f(uSizeLocation, size); gl.drawArrays(gl.TRIANGLES, 0, 6); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resizeCanvas); }; }, [hue, xOffset, speed, intensity, size]); diff --git a/src/content/Backgrounds/RippleGrid/RippleGrid.jsx b/src/content/Backgrounds/RippleGrid/RippleGrid.jsx index 755d6c9b7..f24344f05 100644 --- a/src/content/Backgrounds/RippleGrid/RippleGrid.jsx +++ b/src/content/Backgrounds/RippleGrid/RippleGrid.jsx @@ -201,6 +201,7 @@ void main() { } resize(); + let animationFrameId; const render = t => { uniforms.iTime.value = t * 0.001; @@ -215,13 +216,14 @@ void main() { uniforms.mousePosition.value = [mousePositionRef.current.x, mousePositionRef.current.y]; renderer.render({ scene: mesh }); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); const container = containerRef.current; return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resize); if (mouseInteraction && container) { container.removeEventListener('mousemove', handleMouseMove); diff --git a/src/tailwind/Backgrounds/Lightning/Lightning.jsx b/src/tailwind/Backgrounds/Lightning/Lightning.jsx index 2f5cfaf9c..2beba77e8 100644 --- a/src/tailwind/Backgrounds/Lightning/Lightning.jsx +++ b/src/tailwind/Backgrounds/Lightning/Lightning.jsx @@ -153,6 +153,7 @@ const Lightning = ({ hue = 230, xOffset = 0, speed = 1, intensity = 1, size = 1 const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity'); const uSizeLocation = gl.getUniformLocation(program, 'uSize'); + let animationFrameId; const startTime = performance.now(); const render = () => { resizeCanvas(); @@ -166,11 +167,12 @@ const Lightning = ({ hue = 230, xOffset = 0, speed = 1, intensity = 1, size = 1 gl.uniform1f(uIntensityLocation, intensity); gl.uniform1f(uSizeLocation, size); gl.drawArrays(gl.TRIANGLES, 0, 6); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resizeCanvas); }; }, [hue, xOffset, speed, intensity, size]); diff --git a/src/tailwind/Backgrounds/RippleGrid/RippleGrid.jsx b/src/tailwind/Backgrounds/RippleGrid/RippleGrid.jsx index aa6af2fe0..328678077 100644 --- a/src/tailwind/Backgrounds/RippleGrid/RippleGrid.jsx +++ b/src/tailwind/Backgrounds/RippleGrid/RippleGrid.jsx @@ -200,6 +200,7 @@ void main() { } resize(); + let animationFrameId; const render = t => { uniforms.iTime.value = t * 0.001; @@ -214,13 +215,14 @@ void main() { uniforms.mousePosition.value = [mousePositionRef.current.x, mousePositionRef.current.y]; renderer.render({ scene: mesh }); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); const container = containerRef.current; return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resize); if (mouseInteraction && container) { container.removeEventListener('mousemove', handleMouseMove); diff --git a/src/ts-default/Backgrounds/Lightning/Lightning.tsx b/src/ts-default/Backgrounds/Lightning/Lightning.tsx index 8b0a14605..7f74b2bda 100644 --- a/src/ts-default/Backgrounds/Lightning/Lightning.tsx +++ b/src/ts-default/Backgrounds/Lightning/Lightning.tsx @@ -162,6 +162,7 @@ const Lightning: React.FC = ({ hue = 230, xOffset = 0, speed = 1 const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity'); const uSizeLocation = gl.getUniformLocation(program, 'uSize'); + let animationFrameId: number; const startTime = performance.now(); const render = () => { resizeCanvas(); @@ -175,11 +176,12 @@ const Lightning: React.FC = ({ hue = 230, xOffset = 0, speed = 1 gl.uniform1f(uIntensityLocation, intensity); gl.uniform1f(uSizeLocation, size); gl.drawArrays(gl.TRIANGLES, 0, 6); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resizeCanvas); }; }, [hue, xOffset, speed, intensity, size]); diff --git a/src/ts-default/Backgrounds/RippleGrid/RippleGrid.tsx b/src/ts-default/Backgrounds/RippleGrid/RippleGrid.tsx index 831a5b547..1e5b4cd42 100644 --- a/src/ts-default/Backgrounds/RippleGrid/RippleGrid.tsx +++ b/src/ts-default/Backgrounds/RippleGrid/RippleGrid.tsx @@ -216,6 +216,7 @@ void main() { } resize(); + let animationFrameId: number; const render = (t: number) => { uniforms.iTime.value = t * 0.001; @@ -230,12 +231,13 @@ void main() { uniforms.mousePosition.value = [mousePositionRef.current.x, mousePositionRef.current.y]; renderer.render({ scene: mesh }); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resize); if (mouseInteraction && containerRef.current) { containerRef.current.removeEventListener('mousemove', handleMouseMove); diff --git a/src/ts-tailwind/Backgrounds/Lightning/Lightning.tsx b/src/ts-tailwind/Backgrounds/Lightning/Lightning.tsx index f4f07eb84..d57f56edc 100644 --- a/src/ts-tailwind/Backgrounds/Lightning/Lightning.tsx +++ b/src/ts-tailwind/Backgrounds/Lightning/Lightning.tsx @@ -161,6 +161,7 @@ const Lightning: React.FC = ({ hue = 230, xOffset = 0, speed = 1 const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity'); const uSizeLocation = gl.getUniformLocation(program, 'uSize'); + let animationFrameId: number; const startTime = performance.now(); const render = () => { resizeCanvas(); @@ -174,11 +175,12 @@ const Lightning: React.FC = ({ hue = 230, xOffset = 0, speed = 1 gl.uniform1f(uIntensityLocation, intensity); gl.uniform1f(uSizeLocation, size); gl.drawArrays(gl.TRIANGLES, 0, 6); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resizeCanvas); }; }, [hue, xOffset, speed, intensity, size]); diff --git a/src/ts-tailwind/Backgrounds/RippleGrid/RippleGrid.tsx b/src/ts-tailwind/Backgrounds/RippleGrid/RippleGrid.tsx index 7a0a7d8a7..e249c180f 100644 --- a/src/ts-tailwind/Backgrounds/RippleGrid/RippleGrid.tsx +++ b/src/ts-tailwind/Backgrounds/RippleGrid/RippleGrid.tsx @@ -215,6 +215,7 @@ void main() { } resize(); + let animationFrameId: number; const render = (t: number) => { uniforms.iTime.value = t * 0.001; @@ -229,12 +230,13 @@ void main() { uniforms.mousePosition.value = [mousePositionRef.current.x, mousePositionRef.current.y]; renderer.render({ scene: mesh }); - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); }; - requestAnimationFrame(render); + animationFrameId = requestAnimationFrame(render); return () => { + cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resize); if (mouseInteraction && containerRef.current) { containerRef.current.removeEventListener('mousemove', handleMouseMove);