Meteor Shower Animation Background
Beautiful meteor shower animation background built with React and Canvas. Add dynamic falling meteors to your site with smooth, customizable visuals and minimal performance impact.
Bundui Components
import { Button } from "@/components/ui/button";
import MeteorShower from "@/components/core/backgrounds/meteor-shower";
export default function MeteorShowerAnimationExample() {
return (
<MeteorShower 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-black/80 lg:text-3xl dark:text-white/80">
Bundui Components
</h4>
<Button>Discover Excellence</Button>
</div>
</MeteorShower>
);
}
Installation
Install the following dependencies:
npm install clsx tailwind-merge next-themes
Copy and paste the following code into your project:
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useTheme } from "next-themes";
import { cn } from "@/lib/utils";
interface Meteor {
x: number;
y: number;
size: number;
speed: number;
angle: number;
opacity: number;
tail: { x: number; y: number }[];
tailLength: number;
}
interface ThemeColors {
background: string;
meteorHead: string;
meteorTailStart: string;
meteorTailMiddle: string;
meteorTailEnd: string;
}
export default function MeteorShower({
children,
className
}: {
children: React.ReactNode;
className?: string;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const meteorsRef = useRef<Meteor[]>([]);
const animationRef = useRef<number>(0);
const { theme } = useTheme();
const [mounted, setMounted] = useState(false);
// Avoid hydration mismatch
useEffect(() => {
setMounted(true);
}, []);
// Initialize canvas dimensions
useEffect(() => {
const updateDimensions = () => {
if (typeof window !== "undefined") {
setDimensions({
width: window.innerWidth,
height: window.innerHeight
});
}
};
updateDimensions();
window.addEventListener("resize", updateDimensions);
return () => {
window.removeEventListener("resize", updateDimensions);
};
}, []);
// Main animation logic
useEffect(() => {
if (!canvasRef.current || dimensions.width === 0 || dimensions.height === 0 || !mounted) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Set canvas dimensions
canvas.width = dimensions.width;
canvas.height = dimensions.height;
// Define theme colors
const themeColors: ThemeColors =
theme === "dark"
? {
background: "#000000",
meteorHead: "rgba(255, 255, 255, 1)",
meteorTailStart: "rgba(255, 255, 255, 1)",
meteorTailMiddle: "rgba(255, 240, 200, 0.8)",
meteorTailEnd: "rgba(255, 200, 100, 0.1)"
}
: {
background: "#ffffff", // Light blue sky
meteorHead: "rgba(70, 90, 120, 1)",
meteorTailStart: "rgba(70, 90, 120, 1)",
meteorTailMiddle: "rgba(100, 120, 150, 0.8)",
meteorTailEnd: "rgba(130, 150, 180, 0.1)"
};
// Create meteors with varied starting positions
const createMeteor = (): Meteor => {
// Determine a random starting position across the screen
// We'll use different edges and positions for variety
// Random position factor (0-1)
const positionFactor = Math.random();
// Choose a starting position based on screen dimensions
let x, y;
// Start from left side of screen
if (positionFactor < 0.25) {
x = -20;
y = dimensions.height * Math.random() * 0.7; // Random position on left edge
}
// Start from top of screen
else if (positionFactor < 0.5) {
x = dimensions.width * Math.random();
y = -20; // Just above the top edge
}
// Start from right side of screen
else if (positionFactor < 0.75) {
x = dimensions.width + 20;
y = dimensions.height * Math.random() * 0.7; // Random position on right edge
}
// Start from middle-top area
else {
x = dimensions.width * 0.3 + Math.random() * dimensions.width * 0.4; // Middle 40% of screen width
y = -20;
}
// Random size between 1 and 3
const size = 1 + Math.random() * 10;
// Medium-slow speed (consistent for all meteors)
const speed = 3;
// Fixed angle for consistent direction (45 degrees in radians)
const angle = Math.PI / 4;
// Fixed tail length
const tailLength = 15;
return {
x,
y,
size,
speed,
angle,
opacity: 0.7 + Math.random() * 0.3,
tail: [],
tailLength
};
};
// Initialize meteors
meteorsRef.current = Array.from({ length: 5 }, createMeteor);
// Animation loop
const animate = () => {
// Fill with theme background
ctx.fillStyle = themeColors.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update and draw meteors
meteorsRef.current.forEach((meteor, index) => {
// Update position
meteor.x += Math.cos(meteor.angle) * meteor.speed;
meteor.y += Math.sin(meteor.angle) * meteor.speed;
// Add current position to tail
meteor.tail.unshift({ x: meteor.x, y: meteor.y });
// Limit tail length
if (meteor.tail.length > meteor.tailLength) {
meteor.tail.pop();
}
// Draw meteor tail (gradient line)
if (meteor.tail.length > 1) {
ctx.beginPath();
// Create gradient for tail
const gradient = ctx.createLinearGradient(
meteor.tail[0].x,
meteor.tail[0].y,
meteor.tail[meteor.tail.length - 1].x,
meteor.tail[meteor.tail.length - 1].y
);
gradient.addColorStop(0, themeColors.meteorTailStart.replace("1)", `${meteor.opacity})`));
gradient.addColorStop(
0.3,
themeColors.meteorTailMiddle.replace("0.8)", `${meteor.opacity * 0.8})`)
);
gradient.addColorStop(
1,
themeColors.meteorTailEnd.replace("0.1)", `${meteor.opacity * 0.1})`)
);
ctx.strokeStyle = gradient;
ctx.lineWidth = meteor.size;
// Draw the tail path
ctx.moveTo(meteor.tail[0].x, meteor.tail[0].y);
for (let i = 1; i < meteor.tail.length; i++) {
ctx.lineTo(meteor.tail[i].x, meteor.tail[i].y);
}
ctx.stroke();
}
// Draw meteor head (small circle)
ctx.beginPath();
ctx.arc(meteor.x, meteor.y, meteor.size / 2, 0, Math.PI * 2);
ctx.fillStyle = themeColors.meteorHead.replace("1)", `${meteor.opacity})`);
ctx.fill();
// Reset meteor if it goes off screen
if (meteor.y > dimensions.height || meteor.x < -50 || meteor.x > dimensions.width + 50) {
meteorsRef.current[index] = createMeteor();
}
});
// Add new meteors randomly
if (Math.random() < 0.02 && meteorsRef.current.length < 12) {
meteorsRef.current.push(createMeteor());
}
animationRef.current = requestAnimationFrame(animate);
};
animate();
// Cleanup
return () => {
cancelAnimationFrame(animationRef.current);
};
}, [dimensions, theme, mounted]);
return (
<div className={cn("relative w-full", className)}>
<canvas
ref={canvasRef}
className="absolute h-full w-full"
style={{ display: dimensions.width > 0 ? "block" : "none" }}
/>
<div className="z-10">{children}</div>
</div>
);
}
Update the import paths to match your project setup.
Usage
import { Button } from "@/components/ui/button";
import MeteorShower from "@/components/core/backgrounds/meteor-shower";
export default function MeteorShowerAnimationExample() {
return (
<MeteorShower 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-black/80 lg:text-3xl dark:text-white/80">
Bundui Components
</h4>
<Button>Discover Excellence</Button>
</div>
</MeteorShower>
);
}
Props
Prop | Type | Default |
---|---|---|
className? | string | - |
children | React.ReactNode | - |
Text Gradient Scroll
Discover the allure of animated gradient text, a dynamic UI component that enhances user engagement with smooth color transitions. Built with Tailwind CSS and Motion.
Snowfall Animation Background
Enhance your website with the peaceful atmosphere of the Snowfall Animation Background. Create a captivating design with gently falling snowflakes for a serene and wintery experience.