Docs
Particle Background

Particle Background

An interactive particle system background that responds to mouse movement, creating an engaging tech-focused aesthetic.

Particle Background

A particle network background for your hero sections

Installation

Copy and paste the following code into your project.

components/ui/particle-background.tsx

"use client";
 
import React, { useEffect, useRef, useState } from "react";
 
interface Particle {
  x: number;
  y: number;
  vx: number;
  vy: number;
  size: number;
}
 
interface ParticleNetworkBackgroundProps {
  particleCount?: number;
  particleSize?: number;
  particleColor?: string;
  lineColor?: string;
  maxDistance?: number;
  speed?: number;
  className?: string;
  interactive?: boolean;
}
 
export function ParticleNetworkBackground({
  particleCount = 50,
  particleSize = 2,
  particleColor = "#4a90e2",
  lineColor = "rgba(74, 144, 226, 0.2)",
  maxDistance = 100,
  speed = 1,
  className = "",
  interactive = true,
}: ParticleNetworkBackgroundProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const particlesRef = useRef<Particle[]>([]);
  const mousePositionRef = useRef({ x: 0, y: 0 });
  const isMouseInCanvasRef = useRef(false);
  const animationFrameId = useRef<number | undefined>(undefined);
 
  const initParticles = (width: number, height: number) => {
    const particles: Particle[] = [];
    for (let i = 0; i < particleCount; i++) {
      particles.push({
        x: Math.random() * width,
        y: Math.random() * height,
        vx: (Math.random() - 0.5) * speed,
        vy: (Math.random() - 0.5) * speed,
        size: particleSize,
      });
    }
    particlesRef.current = particles;
  };
 
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
 
    const ctx = canvas.getContext("2d");
    if (!ctx) return;
 
    const updateCanvasSize = () => {
      const rect = canvas.getBoundingClientRect();
      canvas.width = rect.width;
      canvas.height = rect.height;
      initParticles(rect.width, rect.height);
    };
 
    const handleMouseMove = (e: MouseEvent) => {
      if (!interactive) return;
      const rect = canvas.getBoundingClientRect();
      mousePositionRef.current = {
        x: e.clientX - rect.left,
        y: e.clientY - rect.top,
      };
    };
 
    const handleMouseEnter = () => {
      isMouseInCanvasRef.current = true;
    };
 
    const handleMouseLeave = () => {
      isMouseInCanvasRef.current = false;
    };
 
    const animate = () => {
      const { width, height } = canvas;
      ctx.clearRect(0, 0, width, height);
 
      particlesRef.current = particlesRef.current.map((particle) => {
        particle.x += particle.vx;
        particle.y += particle.vy;
 
        if (particle.x < 0 || particle.x > width) particle.vx *= -1;
        if (particle.y < 0 || particle.y > height) particle.vy *= -1;
 
        particle.x = Math.max(0, Math.min(width, particle.x));
        particle.y = Math.max(0, Math.min(height, particle.y));
 
        ctx.beginPath();
        ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
        ctx.fillStyle = particleColor;
        ctx.fill();
 
        return particle;
      });
 
      ctx.beginPath();
      for (let i = 0; i < particlesRef.current.length; i++) {
        const particle1 = particlesRef.current[i];
 
        if (interactive && isMouseInCanvasRef.current) {
          const mouseDistance = Math.hypot(
            mousePositionRef.current.x - particle1.x,
            mousePositionRef.current.y - particle1.y,
          );
          if (mouseDistance < maxDistance) {
            ctx.moveTo(particle1.x, particle1.y);
            ctx.lineTo(mousePositionRef.current.x, mousePositionRef.current.y);
          }
        }
 
        for (let j = i + 1; j < particlesRef.current.length; j++) {
          const particle2 = particlesRef.current[j];
          const distance = Math.hypot(
            particle2.x - particle1.x,
            particle2.y - particle1.y,
          );
 
          if (distance < maxDistance) {
            ctx.moveTo(particle1.x, particle1.y);
            ctx.lineTo(particle2.x, particle2.y);
          }
        }
      }
      ctx.strokeStyle = lineColor;
      ctx.stroke();
 
      animationFrameId.current = requestAnimationFrame(animate);
    };
 
    updateCanvasSize();
    window.addEventListener("resize", updateCanvasSize);
 
    if (interactive) {
      canvas.addEventListener("mousemove", handleMouseMove);
      canvas.addEventListener("mouseenter", handleMouseEnter);
      canvas.addEventListener("mouseleave", handleMouseLeave);
    }
 
    animate();
 
    return () => {
      window.removeEventListener("resize", updateCanvasSize);
      if (interactive) {
        canvas.removeEventListener("mousemove", handleMouseMove);
        canvas.removeEventListener("mouseenter", handleMouseEnter);
        canvas.removeEventListener("mouseleave", handleMouseLeave);
      }
      if (animationFrameId.current) {
        cancelAnimationFrame(animationFrameId.current);
      }
    };
  }, [
    particleColor,
    lineColor,
    maxDistance,
    particleCount,
    particleSize,
    speed,
    interactive,
  ]);
 
  return (
    <canvas
      ref={(node) => {
        canvasRef.current = node;
      }}
      className={`absolute inset-0 h-full w-full ${className}`}
      style={{ background: "transparent" }}
    />
  );
}

Update the import paths to match your project setup.

import ParticleBackground from "@/components/ui/particle-background";

Usage

import ParticleBackground from "@/components/ui/particle-background";
 
export default function Page() {
  return (
    <div className="relative min-h-screen">
      <ParticleBackground
        particleCount={50}
        particleSize={2}
        particleColor="#4a90e2"
        lineColor="rgba(74, 144, 226, 0.2)"
        maxDistance={100}
        speed={1}
        interactive={false}
      />
      <div className="relative z-10">{/* Your content here */}</div>
    </div>
  );
}

Props

PropTypeDefaultDescription
particleCountnumber50The number of particles to display.
particleSizenumber2The size of each particle in pixels.
particleColorstring"#4a90e2"The color of each particle.
lineColorstring"rgba(74, 144, 226, 0.2)"The color of the lines connecting the particles.
maxDistancenumber100The maximum distance between particles for them to connect.
speednumber1The speed of the particle movement.
interactivebooleanfalseWhether to enable mouse interaction with particles.