diff --git a/src/Components/Particles/index.tsx b/src/Components/Particles/index.tsx index a23cdbf..000bf10 100644 --- a/src/Components/Particles/index.tsx +++ b/src/Components/Particles/index.tsx @@ -1,12 +1,25 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Particles, { initParticlesEngine } from "@tsparticles/react"; import { loadSlim } from "@tsparticles/slim"; +import type { Container } from "@tsparticles/engine"; + import { particlesOptions } from "./particlesOptions"; +import { techLogoNames } from "@/Common/techlogos"; + +const BATCH_LOAD_INTERVAL = 1000; +// NOTE: this should be 1 to load all logos, needs fix +const BATCH_SIZE = 1; + export const ParticlesContainer = () => { const [init, setInit] = useState(false); + const [particlesLoaded, setParticlesLoaded] = useState(false); + + const particleContainerRef = useRef(null); + + const [loadedTechLogos, setLoadedTechLogos] = useState([]); // this should be run only once per application lifetime useEffect(() => { @@ -19,17 +32,71 @@ export const ParticlesContainer = () => { .catch(console.error); }, []); + const handleParticlesLoaded = useCallback(async (container?: Container) => { + if (container) { + particleContainerRef.current = container; + setParticlesLoaded(true); + } + return Promise.resolve(); + }, []); + + useEffect(() => { + if (!particlesLoaded) return; + + const interval = setInterval(() => { + const nextIndex = loadedTechLogos.length; + const nextBatch = techLogoNames.slice(nextIndex, nextIndex + BATCH_SIZE); + + if (nextBatch.length === 0) { + clearInterval(interval); + return; + } + + particleContainerRef.current?.particles.addParticle(undefined, { + number: { density: { enable: true }, value: techLogoNames.length }, + opacity: { + value: { min: 0, max: 0.5 }, + }, + move: { + enable: true, + direction: "none", + outModes: { + default: "out", + }, + random: false, + speed: 8, + straight: false, + vibrate: true, + }, + size: { + value: 16, + }, + // READ-MORE: https://particles.js.org/docs/interfaces/tsParticles_Engine.Options_Interfaces_Particles_Shape_IShape.IShape.html + shape: { + options: { + image: nextBatch.map((logoName) => ({ src: `./logos/${logoName}.svg` })), + }, + type: "image", + }, + }); + + setLoadedTechLogos((currentLogos) => [...currentLogos, ...nextBatch]); + }, BATCH_LOAD_INTERVAL); // load a batch of size every seconds + return () => clearInterval(interval); + }, [particlesLoaded, loadedTechLogos]); + const particles = useMemo(() => { if (init) { return ( ); } return null; - }, [init]); + }, [init, handleParticlesLoaded]); return
{particles}
; }; diff --git a/src/Components/Particles/particlesOptions.ts b/src/Components/Particles/particlesOptions.ts index f5c46e9..634c7e4 100644 --- a/src/Components/Particles/particlesOptions.ts +++ b/src/Components/Particles/particlesOptions.ts @@ -1,5 +1,3 @@ -import { techLogos } from "@/Common/techlogos"; - import type { ISourceOptions } from "@tsparticles/engine"; const particlesOptions: ISourceOptions = { @@ -14,53 +12,8 @@ const particlesOptions: ISourceOptions = { zIndex: -1, }, fpsLimit: 60, - particles: { - number: { - density: { - enable: true, - }, - value: 40, - }, - move: { - direction: "none", - enable: true, - outModes: { - default: "out", - }, - random: false, - speed: 3, - straight: false, - }, - opacity: { - animation: { - enable: true, - speed: 0.5, - sync: false, - delay: 1, - }, - value: { min: 0, max: 0.5 }, - }, - size: { - value: 18, - }, - shape: { - // READ-MORE: https://particles.js.org/docs/interfaces/tsParticles_Engine.Options_Interfaces_Particles_Shape_IShape.IShape.html - options: { - image: [], - }, - type: "image", - }, - }, preload: [], - detectRetina: true, }; -const particleOptionsImage = Object.values(techLogos).map(({ src }) => ({ - src, -})); - -// @ts-expect-error-next-line -particlesOptions.particles.shape.options.image = particleOptionsImage; - export { particlesOptions };