Particles

The Particles component creates a subtle animated background effect with floating particles. It’s perfect for adding visual interest to hero sections, landing pages, or any area that needs a dynamic atmosphere.

Usage

<script>
  import Particles from "$lib/components/ui/particles/particles.svelte";
</script>

<Particles />

Examples

Basic Particles

The default particles background effect.

Particles Background

With Content Overlay

Particles as a background with content positioned on top.

magic.mov

A collection of experimental filmmaking tools to enhance your creative process

API Reference

The Particles component doesn’t accept any props as it’s designed to be a full-screen background element.

Implementation

The Particles component uses the HTML Canvas API to create and animate floating particles. It handles initialization, animation, and cleanup automatically:

<script lang="ts">
	import { onMount } from 'svelte';

	interface Particle {
		x: number;
		y: number;
		vx: number;
		vy: number;
		size: number;
		alpha: number;
	}

	let canvas: HTMLCanvasElement;
	let ctx: CanvasRenderingContext2D;
	let particles: Particle[] = [];
	let animationFrame: number;
	let width: number;
	let height: number;

	const PARTICLE_COUNT = 50;
	const PARTICLE_SIZE_RANGE = { min: 2, max: 4 };
	const SPEED_RANGE = { min: -0.5, max: 0.5 };

	function createParticle(): Particle {
		return {
			x: Math.random() * width,
			y: Math.random() * height,
			vx: SPEED_RANGE.min + Math.random() * (SPEED_RANGE.max - SPEED_RANGE.min),
			vy: SPEED_RANGE.min + Math.random() * (SPEED_RANGE.max - SPEED_RANGE.min),
			size:
				PARTICLE_SIZE_RANGE.min +
				Math.random() * (PARTICLE_SIZE_RANGE.max - PARTICLE_SIZE_RANGE.min),
			alpha: 0.1 + Math.random() * 0.4
		};
	}

	function initParticles() {
		particles = Array(PARTICLE_COUNT).fill(null).map(createParticle);
	}

	function updateParticles() {
		particles.forEach((p) => {
			p.x += p.vx;
			p.y += p.vy;

			if (p.x < 0) p.x = width;
			if (p.x > width) p.x = 0;
			if (p.y < 0) p.y = height;
			if (p.y > height) p.y = 0;
		});
	}

	function draw() {
		ctx.clearRect(0, 0, width, height);

		particles.forEach((p) => {
			ctx.beginPath();
			ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
			ctx.fillStyle = `rgba(255, 255, 255, ${p.alpha})`;
			ctx.fill();
		});

		updateParticles();
		animationFrame = requestAnimationFrame(draw);
	}

	function handleResize() {
		width = window.innerWidth;
		height = window.innerHeight;
		canvas.width = width;
		canvas.height = height;
		initParticles();
	}

	onMount(() => {
		ctx = canvas.getContext('2d')!;
		handleResize();
		window.addEventListener('resize', handleResize);
		draw();

		return () => {
			window.removeEventListener('resize', handleResize);
			cancelAnimationFrame(animationFrame);
		};
	});
</script>

<canvas bind:this={canvas} class="pointer-events-none fixed inset-0 -z-10"></canvas>

The component automatically:

  • Initializes particles when mounted
  • Handles window resizing
  • Animates particles with subtle movements
  • Properly cleans up when unmounted to prevent memory leaks

Note that the particles are rendered with a negative z-index (-z-10) to ensure they appear behind other content.