diff --git a/src/content/Animations/ImageTrail/ImageTrail.jsx b/src/content/Animations/ImageTrail/ImageTrail.jsx index f8b4fdf57..a631e9e45 100644 --- a/src/content/Animations/ImageTrail/ImageTrail.jsx +++ b/src/content/Animations/ImageTrail/ImageTrail.jsx @@ -49,11 +49,17 @@ class ImageItem { getRect() { this.rect = this.DOM.el.getBoundingClientRect(); } + + destroy() { + window.removeEventListener('resize', this.resize); + } } class ImageTrailVariant1 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...this.DOM.el.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -79,16 +85,20 @@ class ImageTrailVariant1 { this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -100,7 +110,7 @@ class ImageTrailVariant1 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -143,6 +153,22 @@ class ImageTrailVariant1 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -158,6 +184,8 @@ class ImageTrailVariant1 { class ImageTrailVariant2 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -183,16 +211,20 @@ class ImageTrailVariant2 { this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -204,7 +236,7 @@ class ImageTrailVariant2 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -262,6 +294,22 @@ class ImageTrailVariant2 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -275,6 +323,8 @@ class ImageTrailVariant2 { class ImageTrailVariant3 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -300,15 +350,19 @@ class ImageTrailVariant3 { this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -320,7 +374,7 @@ class ImageTrailVariant3 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -380,6 +434,22 @@ class ImageTrailVariant3 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -393,6 +463,8 @@ class ImageTrailVariant3 { class ImageTrailVariant4 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -417,15 +489,19 @@ class ImageTrailVariant4 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -435,7 +511,7 @@ class ImageTrailVariant4 { this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -512,6 +588,22 @@ class ImageTrailVariant4 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -525,6 +617,8 @@ class ImageTrailVariant4 { class ImageTrailVariant5 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -550,15 +644,19 @@ class ImageTrailVariant5 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -567,7 +665,7 @@ class ImageTrailVariant5 { this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -640,6 +738,22 @@ class ImageTrailVariant5 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -653,6 +767,8 @@ class ImageTrailVariant5 { class ImageTrailVariant6 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -677,15 +793,19 @@ class ImageTrailVariant6 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -697,7 +817,7 @@ class ImageTrailVariant6 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } mapSpeedToSize(speed, minSize, maxSize) { @@ -780,6 +900,22 @@ class ImageTrailVariant6 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -803,6 +939,8 @@ function getNewPosition(position, offset, arr) { class ImageTrailVariant7 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -831,15 +969,19 @@ class ImageTrailVariant7 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -850,7 +992,7 @@ class ImageTrailVariant7 { } if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -905,6 +1047,22 @@ class ImageTrailVariant7 { } } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -917,6 +1075,8 @@ class ImageTrailVariant7 { class ImageTrailVariant8 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -946,15 +1106,19 @@ class ImageTrailVariant8 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -966,7 +1130,7 @@ class ImageTrailVariant8 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -1035,6 +1199,22 @@ class ImageTrailVariant8 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -1063,7 +1243,11 @@ export default function ImageTrail({ items = [], variant = 1 }) { if (!containerRef.current) return; const Cls = variantMap[variant] || variantMap[1]; - new Cls(containerRef.current); + const instance = new Cls(containerRef.current); + + return () => { + instance.destroy(); + }; }, [variant, items]); return ( diff --git a/src/tailwind/Animations/ImageTrail/ImageTrail.jsx b/src/tailwind/Animations/ImageTrail/ImageTrail.jsx index 03b566294..5ddc752d7 100644 --- a/src/tailwind/Animations/ImageTrail/ImageTrail.jsx +++ b/src/tailwind/Animations/ImageTrail/ImageTrail.jsx @@ -47,11 +47,17 @@ class ImageItem { getRect() { this.rect = this.DOM.el.getBoundingClientRect(); } + + destroy() { + window.removeEventListener('resize', this.resize); + } } class ImageTrailVariant1 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...this.DOM.el.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -77,16 +83,20 @@ class ImageTrailVariant1 { this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -98,7 +108,7 @@ class ImageTrailVariant1 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -141,6 +151,22 @@ class ImageTrailVariant1 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -156,6 +182,8 @@ class ImageTrailVariant1 { class ImageTrailVariant2 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -181,16 +209,20 @@ class ImageTrailVariant2 { this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -202,7 +234,7 @@ class ImageTrailVariant2 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -260,6 +292,22 @@ class ImageTrailVariant2 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -273,6 +321,8 @@ class ImageTrailVariant2 { class ImageTrailVariant3 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -298,15 +348,19 @@ class ImageTrailVariant3 { this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -318,7 +372,7 @@ class ImageTrailVariant3 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -378,6 +432,22 @@ class ImageTrailVariant3 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -391,6 +461,8 @@ class ImageTrailVariant3 { class ImageTrailVariant4 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -415,15 +487,19 @@ class ImageTrailVariant4 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -433,7 +509,7 @@ class ImageTrailVariant4 { this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -510,6 +586,22 @@ class ImageTrailVariant4 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -523,6 +615,8 @@ class ImageTrailVariant4 { class ImageTrailVariant5 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -548,15 +642,19 @@ class ImageTrailVariant5 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -565,7 +663,7 @@ class ImageTrailVariant5 { this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -638,6 +736,22 @@ class ImageTrailVariant5 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -651,6 +765,8 @@ class ImageTrailVariant5 { class ImageTrailVariant6 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -675,15 +791,19 @@ class ImageTrailVariant6 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -695,7 +815,7 @@ class ImageTrailVariant6 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } mapSpeedToSize(speed, minSize, maxSize) { @@ -778,6 +898,22 @@ class ImageTrailVariant6 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -801,6 +937,8 @@ function getNewPosition(position, offset, arr) { class ImageTrailVariant7 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -829,15 +967,19 @@ class ImageTrailVariant7 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -848,7 +990,7 @@ class ImageTrailVariant7 { } if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -903,6 +1045,22 @@ class ImageTrailVariant7 { } } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -915,6 +1073,8 @@ class ImageTrailVariant7 { class ImageTrailVariant8 { constructor(container) { this.container = container; + this.rafId = null; + this.destroyed = false; this.DOM = { el: container }; this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img)); this.imagesTotal = this.images.length; @@ -944,15 +1104,19 @@ class ImageTrailVariant8 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender); container.removeEventListener('touchmove', initRender); }; container.addEventListener('mousemove', initRender); container.addEventListener('touchmove', initRender); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } render() { + if (this.destroyed) return; + let distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -964,7 +1128,7 @@ class ImageTrailVariant8 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } showNextImage() { @@ -1033,6 +1197,22 @@ class ImageTrailVariant8 { ); } + destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove); + this.container.removeEventListener('touchmove', this.handlePointerMove); + this.container.removeEventListener('mousemove', this.initRender); + this.container.removeEventListener('touchmove', this.initRender); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -1061,7 +1241,11 @@ export default function ImageTrail({ items = [], variant = 1 }) { if (!containerRef.current) return; const Cls = variantMap[variant] || variantMap[1]; - new Cls(containerRef.current); + const instance = new Cls(containerRef.current); + + return () => { + instance.destroy(); + }; }, [variant, items]); return ( diff --git a/src/ts-default/Animations/ImageTrail/ImageTrail.tsx b/src/ts-default/Animations/ImageTrail/ImageTrail.tsx index c789cf7ed..dd796507e 100644 --- a/src/ts-default/Animations/ImageTrail/ImageTrail.tsx +++ b/src/ts-default/Animations/ImageTrail/ImageTrail.tsx @@ -55,6 +55,10 @@ class ImageItem { private getRect() { this.rect = this.DOM.el.getBoundingClientRect(); } + + public destroy() { + window.removeEventListener('resize', this.resize); + } } class ImageTrailVariant1 { @@ -70,6 +74,10 @@ class ImageTrailVariant1 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -96,15 +104,19 @@ class ImageTrailVariant1 { const rect = this.container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -116,7 +128,7 @@ class ImageTrailVariant1 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -159,6 +171,22 @@ class ImageTrailVariant1 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -185,6 +213,10 @@ class ImageTrailVariant2 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -211,15 +243,19 @@ class ImageTrailVariant2 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -231,7 +267,7 @@ class ImageTrailVariant2 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -286,6 +322,22 @@ class ImageTrailVariant2 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -312,6 +364,10 @@ class ImageTrailVariant3 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -338,15 +394,19 @@ class ImageTrailVariant3 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -358,7 +418,7 @@ class ImageTrailVariant3 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -416,6 +476,22 @@ class ImageTrailVariant3 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -442,6 +518,10 @@ class ImageTrailVariant4 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -468,15 +548,19 @@ class ImageTrailVariant4 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -486,7 +570,7 @@ class ImageTrailVariant4 { this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -566,6 +650,22 @@ class ImageTrailVariant4 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -592,6 +692,10 @@ class ImageTrailVariant5 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; private lastAngle: number; constructor(container: HTMLDivElement) { @@ -620,15 +724,19 @@ class ImageTrailVariant5 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -637,7 +745,7 @@ class ImageTrailVariant5 { this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -710,6 +818,22 @@ class ImageTrailVariant5 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -734,6 +858,10 @@ class ImageTrailVariant6 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -760,15 +888,19 @@ class ImageTrailVariant6 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -780,7 +912,7 @@ class ImageTrailVariant6 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private mapSpeedToSize(speed: number, minSize: number, maxSize: number) { @@ -864,6 +996,22 @@ class ImageTrailVariant6 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -899,6 +1047,10 @@ class ImageTrailVariant7 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; private visibleImagesCount: number; private visibleImagesTotal: number; @@ -930,15 +1082,19 @@ class ImageTrailVariant7 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -949,7 +1105,7 @@ class ImageTrailVariant7 { } if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -1004,6 +1160,22 @@ class ImageTrailVariant7 { } } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -1027,6 +1199,10 @@ class ImageTrailVariant8 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; private rotation: { x: number; y: number }; private cachedRotation: { x: number; y: number }; private zValue: number; @@ -1061,15 +1237,19 @@ class ImageTrailVariant8 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -1081,7 +1261,7 @@ class ImageTrailVariant8 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -1150,6 +1330,22 @@ class ImageTrailVariant8 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -1195,7 +1391,11 @@ export default function ImageTrail({ items = [], variant = 1 }: ImageTrailProps) useEffect(() => { if (!containerRef.current) return; const Cls = variantMap[variant] || variantMap[1]; - new Cls(containerRef.current); + const instance = new Cls(containerRef.current); + + return () => { + instance.destroy(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [variant, items]); diff --git a/src/ts-tailwind/Animations/ImageTrail/ImageTrail.tsx b/src/ts-tailwind/Animations/ImageTrail/ImageTrail.tsx index aada4cfe4..f0d40c37e 100644 --- a/src/ts-tailwind/Animations/ImageTrail/ImageTrail.tsx +++ b/src/ts-tailwind/Animations/ImageTrail/ImageTrail.tsx @@ -54,6 +54,10 @@ class ImageItem { private getRect() { this.rect = this.DOM.el.getBoundingClientRect(); } + + public destroy() { + window.removeEventListener('resize', this.resize); + } } class ImageTrailVariant1 { @@ -69,6 +73,10 @@ class ImageTrailVariant1 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -95,15 +103,19 @@ class ImageTrailVariant1 { const rect = this.container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -115,7 +127,7 @@ class ImageTrailVariant1 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -158,6 +170,22 @@ class ImageTrailVariant1 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -184,6 +212,10 @@ class ImageTrailVariant2 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -210,15 +242,19 @@ class ImageTrailVariant2 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -230,7 +266,7 @@ class ImageTrailVariant2 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -285,6 +321,22 @@ class ImageTrailVariant2 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -311,6 +363,10 @@ class ImageTrailVariant3 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -337,15 +393,19 @@ class ImageTrailVariant3 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -357,7 +417,7 @@ class ImageTrailVariant3 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -415,6 +475,22 @@ class ImageTrailVariant3 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -441,6 +517,10 @@ class ImageTrailVariant4 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -467,15 +547,19 @@ class ImageTrailVariant4 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -485,7 +569,7 @@ class ImageTrailVariant4 { this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -565,6 +649,22 @@ class ImageTrailVariant4 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -591,6 +691,10 @@ class ImageTrailVariant5 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; private lastAngle: number; constructor(container: HTMLDivElement) { @@ -619,15 +723,19 @@ class ImageTrailVariant5 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); if (distance > this.threshold) { this.showNextImage(); @@ -636,7 +744,7 @@ class ImageTrailVariant5 { this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -709,6 +817,22 @@ class ImageTrailVariant5 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -733,6 +857,10 @@ class ImageTrailVariant6 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; constructor(container: HTMLDivElement) { this.container = container; @@ -759,15 +887,19 @@ class ImageTrailVariant6 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -779,7 +911,7 @@ class ImageTrailVariant6 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private mapSpeedToSize(speed: number, minSize: number, maxSize: number) { @@ -863,6 +995,22 @@ class ImageTrailVariant6 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -898,6 +1046,10 @@ class ImageTrailVariant7 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; private visibleImagesCount: number; private visibleImagesTotal: number; @@ -929,15 +1081,19 @@ class ImageTrailVariant7 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3); @@ -948,7 +1104,7 @@ class ImageTrailVariant7 { } if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -1003,6 +1159,22 @@ class ImageTrailVariant7 { } } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -1026,6 +1198,10 @@ class ImageTrailVariant8 { private mousePos: { x: number; y: number }; private lastMousePos: { x: number; y: number }; private cacheMousePos: { x: number; y: number }; + private rafId: number | null = null; + private destroyed = false; + private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void; + private initRender!: (ev: MouseEvent | TouchEvent) => void; private rotation: { x: number; y: number }; private cachedRotation: { x: number; y: number }; private zValue: number; @@ -1060,15 +1236,19 @@ class ImageTrailVariant8 { const rect = container.getBoundingClientRect(); this.mousePos = getLocalPointerPos(ev, rect); this.cacheMousePos = { ...this.mousePos }; - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); container.removeEventListener('mousemove', initRender as EventListener); container.removeEventListener('touchmove', initRender as EventListener); }; container.addEventListener('mousemove', initRender as EventListener); container.addEventListener('touchmove', initRender as EventListener); + this.handlePointerMove = handlePointerMove; + this.initRender = initRender; } private render() { + if (this.destroyed) return; + const distance = getMouseDistance(this.mousePos, this.lastMousePos); this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1); this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1); @@ -1080,7 +1260,7 @@ class ImageTrailVariant8 { if (this.isIdle && this.zIndexVal !== 1) { this.zIndexVal = 1; } - requestAnimationFrame(() => this.render()); + this.rafId = requestAnimationFrame(() => this.render()); } private showNextImage() { @@ -1149,6 +1329,22 @@ class ImageTrailVariant8 { ); } + public destroy() { + this.destroyed = true; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener); + this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener); + this.container.removeEventListener('mousemove', this.initRender as EventListener); + this.container.removeEventListener('touchmove', this.initRender as EventListener); + this.images.forEach(img => { + gsap.killTweensOf(img.DOM.el); + img.destroy(); + }); + } + private onImageActivated() { this.activeImagesCount++; this.isIdle = false; @@ -1194,7 +1390,11 @@ export default function ImageTrail({ items = [], variant = 1 }: ImageTrailProps) useEffect(() => { if (!containerRef.current) return; const Cls = variantMap[variant] || variantMap[1]; - new Cls(containerRef.current); + const instance = new Cls(containerRef.current); + + return () => { + instance.destroy(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [variant, items]);