Svelte Integration
The @tsparticles/svelte package provides a native Svelte component for tsParticles. This guide covers Svelte (with Vite) and SvelteKit, including reactive options, event handling, and multiple instances.
Installation
npm install @tsparticles/svelte @tsparticles/engineFor the full bundle or presets:
npm install tsparticles
npm install @tsparticles/preset-snow
npm install @tsparticles/preset-stars
npm install @tsparticles/preset-confetti
npm install @tsparticles/preset-fireworksBasic Usage
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
let engineInitialised = false;
const particlesInit = async (engine: Engine): Promise<void> => {
await loadFull(engine);
engineInitialised = true;
};
const options: ISourceOptions = {
background: {
color: "#0d47a1",
},
fpsLimit: 120,
particles: {
number: {
value: 80,
},
color: {
value: "#ffffff",
},
shape: {
type: "circle",
},
opacity: {
value: 0.5,
},
size: {
value: { min: 1, max: 5 },
},
links: {
enable: true,
distance: 150,
color: "#ffffff",
opacity: 0.4,
width: 1,
},
move: {
enable: true,
speed: 2,
outModes: "out",
},
},
};
</script>
<Particles
id="tsparticles"
options={options}
on:init={particlesInit}
/>Engine Initialisation
Pass an on:init event handler to load the plugins and presets your app needs:
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type { Engine } from "@tsparticles/engine";
const handleInit = async (event: CustomEvent<Engine>) => {
const engine = event.detail;
await loadFull(engine);
};
</script>
<Particles
id="tsparticles"
options={options}
on:init={handleInit}
/>Alternatively, use the initParticlesEngine utility before mounting:
<script lang="ts">
import Particles, { initParticlesEngine } from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import { onMount } from "svelte";
let ready = false;
onMount(async () => {
await initParticlesEngine(async (engine) => {
await loadFull(engine);
});
ready = true;
});
</script>
{#if ready}
<Particles id="tsparticles" options={options} />
{/if}Snow Effect
npm install @tsparticles/preset-snow<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadSnowPreset } from "@tsparticles/preset-snow";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
const handleInit = async (event: CustomEvent<Engine>) => {
await loadSnowPreset(event.detail);
};
const options: ISourceOptions = {
preset: "snow",
background: {
color: "#1a1a2e",
},
};
</script>
<Particles
id="snow"
{options}
on:init={handleInit}
/>Customise the preset behaviour by merging additional options:
<script lang="ts">
const options: ISourceOptions = {
preset: "snow",
background: { color: "#0f0f23" },
particles: {
move: {
speed: 1.5, // slower snowfall
},
opacity: {
value: 0.8, // more visible flakes
},
},
};
</script>Stars Effect
npm install @tsparticles/preset-stars<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadStarsPreset } from "@tsparticles/preset-stars";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
const handleInit = async (event: CustomEvent<Engine>) => {
await loadStarsPreset(event.detail);
};
const options: ISourceOptions = {
preset: "stars",
background: {
color: "#000000",
},
};
</script>
<Particles
id="stars"
{options}
on:init={handleInit}
/>Interactive Particles
Add mouse hover and click interactivity:
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
const handleInit = async (event: CustomEvent<Engine>) => {
await loadFull(event.detail);
};
const options: ISourceOptions = {
background: {
color: "#0d0d0d",
},
particles: {
number: { value: 100 },
color: { value: "#00d8ff" },
shape: { type: "circle" },
opacity: { value: 0.6 },
size: { value: { min: 1, max: 4 } },
links: {
enable: true,
distance: 120,
color: "#00d8ff",
opacity: 0.3,
width: 1,
},
move: {
enable: true,
speed: 3,
},
},
interactivity: {
events: {
onHover: {
enable: true,
mode: "grab",
},
onClick: {
enable: true,
mode: "push",
},
},
modes: {
grab: {
distance: 180,
links: { opacity: 0.5 },
},
push: {
quantity: 4,
},
},
},
};
</script>
<Particles
id="interactive"
{options}
on:init={handleInit}
/>Event Handling
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type { Container, Engine } from "@tsparticles/engine";
let container: Container;
const handleInit = async (event: CustomEvent<Engine>) => {
await loadFull(event.detail);
};
const handleLoaded = (event: CustomEvent<Container>) => {
container = event.detail;
console.log("Container loaded", container);
};
const pause = () => container?.pause();
const resume = () => container?.play();
const destroy = () => container?.destroy();
</script>
<div>
<button on:click={pause}>Pause</button>
<button on:click={resume}>Resume</button>
<button on:click={destroy}>Destroy</button>
</div>
<Particles
id="tsparticles"
options={options}
on:init={handleInit}
on:particlesLoaded={handleLoaded}
/>| Event | Detail | Fires |
|---|---|---|
on:init | Engine | After the engine is initialised |
on:particlesLoaded | Container | After the container is fully ready |
TypeScript Example
Full typed component:
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type {
Container,
Engine,
ISourceOptions,
} from "@tsparticles/engine";
let particlesContainer: Container | undefined;
const options: ISourceOptions = {
background: {
color: "#1e1e2e",
},
fpsLimit: 60,
particles: {
color: {
value: "#cdd6f4",
},
links: {
color: "#cdd6f4",
distance: 150,
enable: true,
opacity: 0.3,
width: 1,
},
move: {
enable: true,
speed: 1.5,
},
number: {
value: 60,
},
opacity: {
value: 0.6,
},
size: {
value: { min: 1, max: 3 },
},
},
interactivity: {
events: {
onHover: {
enable: true,
mode: "repulse",
},
},
modes: {
repulse: {
distance: 100,
},
},
},
detectRetina: true,
};
const handleInit = async (event: CustomEvent<Engine>) => {
await loadFull(event.detail);
};
const handleLoaded = (event: CustomEvent<Container>) => {
particlesContainer = event.detail;
};
</script>
<Particles
id="tsparticles"
{options}
on:init={handleInit}
on:particlesLoaded={handleLoaded}
/>Dynamic Options
Reactive options update the particles without recreating the instance:
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
let color = "#ff0000";
const handleInit = async (event: CustomEvent<Engine>) => {
await loadFull(event.detail);
};
$: options = {
background: {
color: "#000000",
},
particles: {
color: {
value: color,
},
links: {
color: color,
enable: true,
distance: 150,
},
number: {
value: 60,
},
move: {
enable: true,
speed: 2,
},
size: {
value: { min: 1, max: 3 },
},
},
} satisfies ISourceOptions;
</script>
<div>
<label>
Particle Color:
<input type="color" bind:value={color} />
</label>
</div>
<Particles
id="dynamic"
{options}
on:init={handleInit}
/>The $: reactive declaration recomputes options whenever color changes, and the Particles component picks up the new configuration automatically.
Multiple Instances
Render several independent particle systems on the same page:
<script lang="ts">
import Particles from "@tsparticles/svelte";
import { loadFull } from "tsparticles";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
const handleInit = async (event: CustomEvent<Engine>) => {
await loadFull(event.detail);
};
const fireOptions: ISourceOptions = {
background: { color: "#1a0000" },
particles: {
color: { value: "#ff4500" },
number: { value: 40 },
move: { enable: true, speed: 1 },
size: { value: { min: 2, max: 6 } },
opacity: { value: 0.8 },
},
};
const waterOptions: ISourceOptions = {
background: { color: "#000d1a" },
particles: {
color: { value: "#00bfff" },
number: { value: 60 },
move: { enable: true, speed: 0.5, direction: "top" },
size: { value: { min: 1, max: 3 } },
opacity: { value: 0.5 },
},
};
</script>
<div style="display: grid; grid-template-columns: 1fr 1fr; height: 100vh;">
<div style="position: relative;">
<Particles id="fire" options={fireOptions} on:init={handleInit} />
</div>
<div style="position: relative;">
<Particles id="water" options={waterOptions} on:init={handleInit} />
</div>
</div>Each <Particles> component gets its own id, canvas, and engine context.
SvelteKit Usage
In SvelteKit, the canvas requires the browser environment. Disable SSR for the component:
<script lang="ts">
import { browser } from "$app/environment";
import { onMount } from "svelte";
let Component: typeof import("@tsparticles/svelte").default;
onMount(async () => {
if (browser) {
const module = await import("@tsparticles/svelte");
Component = module.default;
}
});
</script>
{#if Component}
<svelte:component this={Component} id="tsparticles" options={options} />
{/if}Or wrap the import in a client-only component. For SvelteKit 2+, you can also use the vite-plugin-svelte SSR excludes.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | "tsparticles" | Canvas element ID |
options | ISourceOptions | {} | Particle configuration object |
url | string | — | URL to a remote JSON config |
| Event | Detail | Description |
|---|---|---|
on:init | Engine | Fires when the engine is initialised (use to load plugins) |
on:particlesLoaded | Container | Fires when the container is fully ready |
Troubleshooting
- Canvas not visible — Ensure the parent container has explicit dimensions (
height: 100%,height: 100vh, or a fixed pixel value). loadFull is not a function— Verifytsparticlesis installed and that you're importingloadFullfromtsparticles(not@tsparticles/engine).- Reactivity not working — Make sure
optionsis a reactive variable ($:orletbound to a reactive source). Plainconstvalues will not update. - SvelteKit blank screen — Import
@tsparticles/sveltedynamically or usebrowserguard as shown in the SvelteKit section above. - TypeScript errors for
event.detail— UseCustomEvent<Engine>andCustomEvent<Container>types for the event handlers.
