bundui svg logo
Bundui

Fireworks Background

Add animated fireworks to your site background with this festive component. Perfect for celebrations. Built with Tailwind CSS and Framer Motion.

Bundui Components

import { Button } from "@/components/ui/button";
import FireworksBackground from "@/components/core/backgrounds/fireworks";

export default function FireworksBackgroundExample() {
  return (
    <FireworksBackground className="flex aspect-16/9 items-center justify-center">
      <div className="z-10 space-y-4 text-center lg:space-y-6">
        <h4 className="text-2xl font-semibold text-white/80 lg:text-3xl">Bundui Components</h4>
        <Button className="bg-white! text-black">Discover Excellence</Button>
      </div>
    </FireworksBackground>
  );
}

Installation

Install the following dependencies:
npm install clsx tailwind-merge
Copy and paste the following code into your project:
"use client";

import React, { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";

interface Particle {
  x: number;
  y: number;
  color: string;
  velocity: {
    x: number;
    y: number;
  };
  alpha: number;
  lifetime: number;
  size: number;
}

interface Firework {
  x: number;
  y: number;
  color: string;
  velocity: {
    x: number;
    y: number;
  };
  particles: Particle[];
  exploded: boolean;
  timeToExplode: number;
}

function FireworksBackground({
  children,
  className,
}: {
  children: React.ReactNode;
  className?: string;
}) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const fireworksRef = useRef<Firework[]>([]);
  const animationFrameRef = useRef<number>(0);
  const lastFireworkTimeRef = useRef<number>(Date.now());

  const colors = [
    "#9b87f5", // purple
    "#D946EF", // magenta
    "#F97316", // orange
    "#0EA5E9", // blue
    "#ea384c", // red
    "#10B981", // green
    "#FCD34D", // yellow
  ];

  const createFirework = (x?: number, y?: number, targetY?: number) => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const startX = x || Math.random() * canvas.width;
    const startY = canvas.height;
    const color = colors[Math.floor(Math.random() * colors.length)];
    const angle = (Math.random() * Math.PI) / 2 - Math.PI / 4; // Random angle between -45 and 45 degrees
    const velocity = 6 + Math.random() * 4; // Random velocity between 6 and 10

    const target = targetY || canvas.height * (0.1 + Math.random() * 0.4); // Target height between 10% and 50% of canvas height

    const firework: Firework = {
      x: startX,
      y: startY,
      color,
      velocity: {
        x: Math.sin(angle) * velocity,
        y: -Math.cos(angle) * velocity * 1.5,
      },
      particles: [],
      exploded: false,
      timeToExplode: target,
    };

    fireworksRef.current.push(firework);
  };

  const explodeFirework = (firework: Firework) => {
    const particleCount = 60 + Math.floor(Math.random() * 40);

    for (let i = 0; i < particleCount; i++) {
      const angle = Math.random() * Math.PI * 2;
      const velocity = Math.random() * 5 + 1;

      firework.particles.push({
        x: firework.x,
        y: firework.y,
        color: firework.color,
        velocity: {
          x: Math.cos(angle) * velocity * (0.5 + Math.random()),
          y: Math.sin(angle) * velocity * (0.5 + Math.random()),
        },
        alpha: 1,
        lifetime: Math.random() * 30 + 30,
        size: Math.random() * 3 + 1,
      });
    }
  };

  const updateAndDraw = () => {
    const canvas = canvasRef.current;
    const ctx = canvas?.getContext("2d");

    if (!canvas || !ctx) return;

    ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const currentFireworks = fireworksRef.current;
    for (let i = 0; i < currentFireworks.length; i++) {
      const firework = currentFireworks[i];

      if (!firework.exploded) {
        firework.x += firework.velocity.x;
        firework.y += firework.velocity.y;
        firework.velocity.y += 0.1;

        ctx.beginPath();
        ctx.arc(firework.x, firework.y, 3, 0, Math.PI * 2);
        ctx.fillStyle = firework.color;
        ctx.fill();

        if (
          firework.y <= firework.timeToExplode ||
          firework.velocity.y >= 0 ||
          firework.x < 0 ||
          firework.x > canvas.width
        ) {
          if (firework.y > 0 && firework.y < canvas.height) {
            explodeFirework(firework);
          }

          firework.exploded = true;
        }
      } else {
        for (let j = 0; j < firework.particles.length; j++) {
          const particle = firework.particles[j];

          particle.x += particle.velocity.x;
          particle.y += particle.velocity.y;
          particle.velocity.y += 0.05;
          particle.alpha -= 1 / particle.lifetime;

          if (particle.alpha <= 0.1) {
            firework.particles.splice(j, 1);
            j--;
            continue;
          }

          ctx.globalAlpha = particle.alpha;
          ctx.beginPath();
          ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
          ctx.fillStyle = particle.color;
          ctx.fill();
          ctx.globalAlpha = 1;
        }

        if (firework.particles.length === 0) {
          currentFireworks.splice(i, 1);
          i--;
        }
      }
    }

    const now = Date.now();
    if (now - lastFireworkTimeRef.current > 1000 + Math.random() * 2000) {
      const numberOfFireworks = Math.floor(Math.random() * 2) + 1;
      for (let i = 0; i < numberOfFireworks; i++) {
        createFirework();
      }
      lastFireworkTimeRef.current = now;
    }

    animationFrameRef.current = requestAnimationFrame(updateAndDraw);
  };

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const updateCanvasSize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    };

    updateCanvasSize();
    window.addEventListener("resize", updateCanvasSize);

    for (let i = 0; i < 3; i++) {
      createFirework();
    }
    lastFireworkTimeRef.current = Date.now();

    animationFrameRef.current = requestAnimationFrame(updateAndDraw);

    return () => {
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
      }
      window.removeEventListener("resize", updateCanvasSize);
    };
  }, []);

  return (
    <div className={cn("relative w-full", className)}>
      <canvas ref={canvasRef} className="absolute w-full inset-0 h-full" />
      {children}
    </div>
  );
}

export default FireworksBackground;
Update the import paths to match your project setup.

Usage

import { Button } from "@/components/ui/button";
import FireworksBackground from "@/components/core/backgrounds/fireworks";

export default function FireworksBackgroundExample() {
  return (
    <FireworksBackground className="flex aspect-16/9 items-center justify-center">
      <div className="z-10 space-y-4 text-center lg:space-y-6">
        <h4 className="text-2xl font-semibold text-white/80 lg:text-3xl">Bundui Components</h4>
        <Button className="bg-white! text-black">Discover Excellence</Button>
      </div>
    </FireworksBackground>
  );
}

Props

PropTypeDefault
className?
string
-
children
React.ReactNode
-