javascriptbackgroundastrojstsparticles

How to toggle tsparticles theme between dark and light mode on Astro JS?


I am using tsparticles for my website and I have two json files that are for light and dark mode, the dark mode version is provided below:

{
  "fullScreen": {
    "enable": true,
    "zIndex": -1
  },
  "fpsLimit": 120,
  "detectRetina": true,
  "particles": {
    "number": {
      "value": 225,
      "density": {
        "enable": true,
        "area": 1500
      }
    },
    "color": {
      "value": [
        "#f56565",
        "#fd7e14"
      ]
    },
    "opacity": {
      "value": 0.8,
      "random": {
        "enable": true,
        "minimumValue": 0.5
      }
    },
    "size": {
      "value": {
        "min": 1.5,
        "max": 3.5
      }
    },
    "links": {
      "enable": true,
      "distance": 100,
      "color": "#f56565",
      "triangles": {
        "enable": true,
        "opacity": 0.1
      }
    },
    "move": {
      "enable": true,
      "speed": 10,
      "direction": "none",
      "random": true,
      "outModes": {
        "default": "out",
        "bottom": "out",
        "left": "out",
        "right": "out",
        "top": "out"
      }
    }
  },
  "interactivity": {
    "detectsOn": "window",
    "events": {
      "onHover": {
        "enable": true,
        "mode": "repulse"
      },
      "onClick": {
        "enable": true,
        "mode": "push"
      }
    },
    "modes": {
      "repulse": {
        "distance": 100,
        "duration": 0.4,
        "factor": 100,
        "speed": 1,
        "maxSpeed": 5,
        "easing": "ease-out-quad"
      }
    }
  },
  "background": {
    "color": {
      "value": "#000000"
    }
  }
}

And the general rendering of this gets done in the layout as I want it on all pages:

<body class="flex flex-col min-h-screen bg-white dark:bg-black text-black dark:text-white">
    <Header />
    <Particles id="tsparticles" options={json} init="particlesInit" />
    <main class="flex-grow max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8" transition:animate="slide">
      <h1 class="text-4xl md:text-5xl font-bold mb-8 bg-gradient-to-r dark:from-red-500 dark:to-orange-500 bg-clip-text text-transparent text-center from-[#2d388a] to-[#00aeef]">{pageTitle}</h1>
      <slot />
    </main>
    <Footer />
  </body>

There is very little online about this particular issue that's up-to-date, and I am not really sure how to setup a light and dark mode button because of this. How can I possibly enable a dark and light mode ability for my tsparticles background that doesn't hinder performance so much?

I tried to get a working light and dark mode button working but it would only affect all elements besides the background.


Solution

  • I resolved the issue after reading this article from 2020: https://dev.to/tsparticles/how-to-use-tsparticles-52k

    Basically, when you use tsParticles in astro in a similar way to the following:

    <body class="flex flex-col min-h-screen bg-white text-black dark:bg-black dark:text-white">
        <Header />
        <Particles id="tsparticles" init="particlesInit" />   
        <main class="flex-grow max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8" transition:animate="slide">
          <h1 class="text-4xl md:text-5xl font-bold mb-8 bg-gradient-to-r bg-clip-text text-transparent text-center from-[#2d388a] to-[#00aeef] dark:from-red-500 dark:to-orange-500">{pageTitle}</h1>
          <slot />
        </main>
        <Footer />
      </body>
    

    Remove the options={json} bit and instead in some js/ts file you can use tsParticles.load() in a similar way provided below (the file that handles my dark/light mode logic):

    import { tsParticles, type IOptions, type RecursivePartial } from "tsparticles-engine";
    import lightModeJson from '../data/particles.triangles.light.json';
    import darkModeJson from '../data/particles.triangles.dark.json';
    
    const sunIcon = document.querySelector('.sun');
    const moonIcon = document.querySelector('.moon');
    
    const setThemeIcons = (theme: string) => {
      if (theme === 'dark') {
        document.documentElement.classList.add('dark');
        sunIcon?.classList.remove('hidden');
        moonIcon?.classList.add('hidden');
      } else {
        document.documentElement.classList.remove('dark');
        sunIcon?.classList.add('hidden');
        moonIcon?.classList.remove('hidden');
      }
    };
    
    // Function to handle loading particles options
    const setBackgroundParticles = (theme: string) => {
        if(theme === 'dark') {
            tsParticles.load("tsparticles", darkModeJson as RecursivePartial<IOptions>);
        } else {
            tsParticles.load("tsparticles", lightModeJson as RecursivePartial<IOptions>)
        }
    }
    
    // Initialize theme
    const initTheme = () => {
      const storedTheme = localStorage.getItem('theme');
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      const theme = storedTheme ?? (prefersDark ? 'dark' : 'light');
      setBackgroundParticles(theme);
      setThemeIcons(theme);
    };
    
    // Function for event listener
    const changeTheme = () => {
      const isDark = document.documentElement.classList.contains('dark');
      const newTheme = isDark ? 'light' : 'dark';
      localStorage.setItem('theme', newTheme);
      setBackgroundParticles(newTheme);
      setThemeIcons(newTheme);
    };
    
    // Run on initial load
    initTheme();
    
    // Event listeners
    sunIcon?.addEventListener('click', changeTheme);
    moonIcon?.addEventListener('click', changeTheme);
    
    // Re-run when Astro navigates client-side
    document.addEventListener('astro:page-load', initTheme);
    

    The code above will also allow for dark/light mode to persist throughout other pages. It is very important that you make sure that astro:page-load is the event to listen to and not the DOM if you are prefetching pages.