Skip to main content
Index

Animated Background Particles in React

12/25/2023Preet Suthar11 min read

Introduction #

Adding interactive and animated backgrounds can elevate the user experience of any web application. In this post, we will explore how to create a customizable animated background particles component in React using TypeScript. The component will support various properties for customization, making it versatile for different use cases.

Features #

The BackgroundParticles component provides:

  • Fully animated particle effects.
  • Customization options for particle quantity, size, color, movement, and more.
  • Performance optimization through efficient canvas rendering.
  • Mouse interaction for dynamic particle behavior.

Let’s dive into the implementation step by step.


Step 1: Setting Up the Component #

We’ll start by creating the BackgroundParticles component with basic props and hooks to manage canvas rendering.

import React, { useEffect, useRef, useState } from "react";

interface ParticlesProps {
  className?: string;
  quantity?: number;
  staticity?: number;
  ease?: number;
  size?: number;
  refresh?: boolean;
  color?: string;
  vx?: number;
  vy?: number;
}

const BackgroundParticles: React.FC<ParticlesProps> = ({
  className = "",
  quantity = 100,
  staticity = 50,
  ease = 50,
  size = 0.4,
  refresh = false,
  color = "#ffffff",
  vx = 0,
  vy = 0,
}) => {
  interface MousePosition {
    x: number;
    y: number;
  }

  const MousePosition = (): MousePosition => {
    const [mousePosition, setMousePosition] =
      useState <
      MousePosition >
      {
        x: 0,
        y: 0,
      };

    useEffect(() => {
      const handleMouseMove = (event: MouseEvent) => {
        setMousePosition({ x: event.clientX, y: event.clientY });
      };

      window.addEventListener("mousemove", handleMouseMove);

      return () => {
        window.removeEventListener("mousemove", handleMouseMove);
      };
    }, []);

    return mousePosition;
  };

  // Rest of the component follows...
};

export default BackgroundParticles;

Step 2: Managing the Canvas and Particles #

The component uses a canvas element to render particles. We handle rendering and animation through requestAnimationFrame for smooth transitions.

Here’s how we initialize the canvas and draw the particles:

const canvasRef = useRef<HTMLCanvasElement>(null);
const canvasContainerRef = useRef<HTMLDivElement>(null);
const context = useRef<CanvasRenderingContext2D | null>(null);
const circles = useRef<any[]>([]);
const mousePosition = MousePosition();
const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 });
const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1;

useEffect(() => {
  if (canvasRef.current) {
    context.current = canvasRef.current.getContext("2d");
  }
  initCanvas();
  animate();
  window.addEventListener("resize", initCanvas);

  return () => {
    window.removeEventListener("resize", initCanvas);
  };
}, [color]);

const initCanvas = () => {
  resizeCanvas();
  drawParticles();
};

const resizeCanvas = () => {
  if (canvasContainerRef.current && canvasRef.current && context.current) {
    circles.current.length = 0;
    canvasSize.current.w = canvasContainerRef.current.offsetWidth;
    canvasSize.current.h = canvasContainerRef.current.offsetHeight;
    canvasRef.current.width = canvasSize.current.w * dpr;
    canvasRef.current.height = canvasSize.current.h * dpr;
    canvasRef.current.style.width = `${canvasSize.current.w}px`;
    canvasRef.current.style.height = `${canvasSize.current.h}px`;
    context.current.scale(dpr, dpr);
  }
};

Step 3: Particle Animation #

The animation logic determines how the particles move and interact with the mouse position.

const animate = () => {
  clearContext();
  circles.current.forEach((circle, i) => {
    const edge = [
      circle.x + circle.translateX - circle.size,
      canvasSize.current.w - circle.x - circle.translateX - circle.size,
      circle.y + circle.translateY - circle.size,
      canvasSize.current.h - circle.y - circle.translateY - circle.size,
    ];
    const closestEdge = edge.reduce((a, b) => Math.min(a, b));
    const remapClosestEdge = parseFloat(
      remapValue(closestEdge, 0, 20, 0, 1).toFixed(2),
    );
    if (remapClosestEdge > 1) {
      circle.alpha += 0.02;
      if (circle.alpha > circle.targetAlpha) {
        circle.alpha = circle.targetAlpha;
      }
    } else {
      circle.alpha = circle.targetAlpha * remapClosestEdge;
    }
    circle.x += circle.dx + vx;
    circle.y += circle.dy + vy;
    circle.translateX +=
      (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) /
      ease;
    circle.translateY +=
      (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) /
      ease;

    drawCircle(circle, true);

    if (
      circle.x < -circle.size ||
      circle.x > canvasSize.current.w + circle.size ||
      circle.y < -circle.size ||
      circle.y > canvasSize.current.h + circle.size
    ) {
      circles.current.splice(i, 1);
      const newCircle = circleParams();
      drawCircle(newCircle);
    }
  });
  window.requestAnimationFrame(animate);
};

Step 4: Using the Component #

To use the component, simply import and render it in your application.

<div className="w-screen h-screen absolute">
  <BackgroundParticles quantity={300} />
</div>

Conclusion #

The BackgroundParticles component is a flexible and visually appealing feature to add to any React application. With its customizable properties, you can tweak it to suit a variety of contexts, from landing pages to dashboard backgrounds.

Feel free to experiment with the settings and integrate it into your next project.