From d0bf23f62b617efa021cf7f3583bbac0d460bcc1 Mon Sep 17 00:00:00 2001 From: Nitesh Agarwal Date: Fri, 29 May 2026 16:45:53 +0530 Subject: [PATCH] Fix WebGL/canvas resource leaks on unmount across Three.js and canvas components --- src/components/3d/HeaderScene.tsx | 34 ++++++++++++++++++++++++---- src/components/ui/ParticleSystem.tsx | 8 ++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/components/3d/HeaderScene.tsx b/src/components/3d/HeaderScene.tsx index 4e82c169..728902d1 100644 --- a/src/components/3d/HeaderScene.tsx +++ b/src/components/3d/HeaderScene.tsx @@ -48,7 +48,6 @@ function Model({ color }: { color: string }) { const box = mesh.geometry.boundingBox!; const size = new THREE.Vector3(); box.getSize(size); - // Use diagonal length as a proxy for size to handle flat objects better const diagonal = size.length(); if (diagonal > maxVolume) { @@ -58,18 +57,24 @@ function Model({ color }: { color: string }) { } }); + // Collect created materials so we can dispose on cleanup + const createdMaterials: THREE.MeshStandardMaterial[] = []; + // Second pass: apply materials scene.traverse((child) => { if ((child as THREE.Mesh).isMesh) { const mesh = child as THREE.Mesh; const isBackground = mesh.uuid === largestMeshId; - // Background: Dark Blue Front (#0B1120), Gold Side (#FFB800) - // D/Content: White Front (#FFFFFF), White Side (#FFFFFF) const frontColor = isBackground ? '#0B1120' : '#FFFFFF'; const sideColor = isBackground ? color : '#FFFFFF'; - // Clone material to avoid affecting other instances if any + // Dispose old material before replacing to free GPU memory + if (mesh.material) { + const old = mesh.material as THREE.Material; + old.dispose(); + } + const material = new THREE.MeshStandardMaterial({ color: new THREE.Color(sideColor), roughness: 0.2, @@ -111,8 +116,14 @@ function Model({ color }: { color: string }) { }; mesh.material = material; + createdMaterials.push(material); } }); + + // Cleanup: dispose all materials we created when color/scene changes or on unmount + return () => { + createdMaterials.forEach((mat) => mat.dispose()); + }; }, [scene, color]); return ( @@ -154,6 +165,21 @@ function FallbackGeometry({ color }: { color: string }) { } }); + // Dispose geometry and material on unmount to free WebGL resources + useEffect(() => { + const mesh = meshRef.current; + return () => { + if (mesh) { + mesh.geometry.dispose(); + if (Array.isArray(mesh.material)) { + mesh.material.forEach((m) => m.dispose()); + } else { + mesh.material.dispose(); + } + } + }; + }, []); + return ( {/* Elegant futuristic floating metallic Torus Knot */} diff --git a/src/components/ui/ParticleSystem.tsx b/src/components/ui/ParticleSystem.tsx index 30577aff..7916e5b6 100644 --- a/src/components/ui/ParticleSystem.tsx +++ b/src/components/ui/ParticleSystem.tsx @@ -69,17 +69,19 @@ export default function ParticleSystem() { animationFrameId = requestAnimationFrame(drawParticles); }; - window.addEventListener('resize', () => { + const handleResize = () => { resizeCanvas(); createParticles(); - }); + }; + + window.addEventListener('resize', handleResize); resizeCanvas(); createParticles(); drawParticles(); return () => { - window.removeEventListener('resize', resizeCanvas); + window.removeEventListener('resize', handleResize); cancelAnimationFrame(animationFrameId); }; }, []);