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.
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.