Shooting Stars and Stars Background
A shooting star animation on top of a starry background, as seen on figmaplug.in
Shooting StarxStar Background
Installation
Install util dependencies
npm i motion clsx tailwind-merge
Add util file
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code for Shooting Stars
components/ui/shooting-stars.tsx
"use client";
import { cn } from "@/lib/utils";
import React, { useEffect, useState, useRef } from "react";
interface ShootingStar {
id: number;
x: number;
y: number;
angle: number;
scale: number;
speed: number;
distance: number;
}
interface ShootingStarsProps {
minSpeed?: number;
maxSpeed?: number;
minDelay?: number;
maxDelay?: number;
starColor?: string;
trailColor?: string;
starWidth?: number;
starHeight?: number;
className?: string;
}
const getRandomStartPoint = () => {
const side = Math.floor(Math.random() * 4);
const offset = Math.random() * window.innerWidth;
switch (side) {
case 0:
return { x: offset, y: 0, angle: 45 };
case 1:
return { x: window.innerWidth, y: offset, angle: 135 };
case 2:
return { x: offset, y: window.innerHeight, angle: 225 };
case 3:
return { x: 0, y: offset, angle: 315 };
default:
return { x: 0, y: 0, angle: 45 };
}
};
export const ShootingStars: React.FC<ShootingStarsProps> = ({
minSpeed = 10,
maxSpeed = 30,
minDelay = 1200,
maxDelay = 4200,
starColor = "#9E00FF",
trailColor = "#2EB9DF",
starWidth = 10,
starHeight = 1,
className,
}) => {
const [star, setStar] = useState<ShootingStar | null>(null);
const svgRef = useRef<SVGSVGElement>(null);
useEffect(() => {
const createStar = () => {
const { x, y, angle } = getRandomStartPoint();
const newStar: ShootingStar = {
id: Date.now(),
x,
y,
angle,
scale: 1,
speed: Math.random() * (maxSpeed - minSpeed) + minSpeed,
distance: 0,
};
setStar(newStar);
const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay;
setTimeout(createStar, randomDelay);
};
createStar();
return () => {};
}, [minSpeed, maxSpeed, minDelay, maxDelay]);
useEffect(() => {
const moveStar = () => {
if (star) {
setStar((prevStar) => {
if (!prevStar) return null;
const newX =
prevStar.x +
prevStar.speed * Math.cos((prevStar.angle * Math.PI) / 180);
const newY =
prevStar.y +
prevStar.speed * Math.sin((prevStar.angle * Math.PI) / 180);
const newDistance = prevStar.distance + prevStar.speed;
const newScale = 1 + newDistance / 100;
if (
newX < -20 ||
newX > window.innerWidth + 20 ||
newY < -20 ||
newY > window.innerHeight + 20
) {
return null;
}
return {
...prevStar,
x: newX,
y: newY,
distance: newDistance,
scale: newScale,
};
});
}
};
const animationFrame = requestAnimationFrame(moveStar);
return () => cancelAnimationFrame(animationFrame);
}, [star]);
return (
<svg
ref={svgRef}
className={cn("w-full h-full absolute inset-0", className)}
>
{star && (
<rect
key={star.id}
x={star.x}
y={star.y}
width={starWidth * star.scale}
height={starHeight}
fill="url(#gradient)"
transform={`rotate(${star.angle}, ${
star.x + (starWidth * star.scale) / 2
}, ${star.y + starHeight / 2})`}
/>
)}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style={{ stopColor: trailColor, stopOpacity: 0 }} />
<stop
offset="100%"
style={{ stopColor: starColor, stopOpacity: 1 }}
/>
</linearGradient>
</defs>
</svg>
);
};
Copy the source code for Stars Background
components/ui/stars-background.tsx
"use client";
import { cn } from "@/lib/utils";
import React, {
useState,
useEffect,
useRef,
RefObject,
useCallback,
} from "react";
interface StarProps {
x: number;
y: number;
radius: number;
opacity: number;
twinkleSpeed: number | null;
}
interface StarBackgroundProps {
starDensity?: number;
allStarsTwinkle?: boolean;
twinkleProbability?: number;
minTwinkleSpeed?: number;
maxTwinkleSpeed?: number;
className?: string;
}
export const StarsBackground: React.FC<StarBackgroundProps> = ({
starDensity = 0.00015,
allStarsTwinkle = true,
twinkleProbability = 0.7,
minTwinkleSpeed = 0.5,
maxTwinkleSpeed = 1,
className,
}) => {
const [stars, setStars] = useState<StarProps[]>([]);
const canvasRef: RefObject<HTMLCanvasElement> =
useRef<HTMLCanvasElement>(null);
const generateStars = useCallback(
(width: number, height: number): StarProps[] => {
const area = width * height;
const numStars = Math.floor(area * starDensity);
return Array.from({ length: numStars }, () => {
const shouldTwinkle =
allStarsTwinkle || Math.random() < twinkleProbability;
return {
x: Math.random() * width,
y: Math.random() * height,
radius: Math.random() * 0.05 + 0.5,
opacity: Math.random() * 0.5 + 0.5,
twinkleSpeed: shouldTwinkle
? minTwinkleSpeed +
Math.random() * (maxTwinkleSpeed - minTwinkleSpeed)
: null,
};
});
},
[
starDensity,
allStarsTwinkle,
twinkleProbability,
minTwinkleSpeed,
maxTwinkleSpeed,
]
);
useEffect(() => {
const updateStars = () => {
if (canvasRef.current) {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const { width, height } = canvas.getBoundingClientRect();
canvas.width = width;
canvas.height = height;
setStars(generateStars(width, height));
}
};
updateStars();
const resizeObserver = new ResizeObserver(updateStars);
if (canvasRef.current) {
resizeObserver.observe(canvasRef.current);
}
return () => {
if (canvasRef.current) {
resizeObserver.unobserve(canvasRef.current);
}
};
}, [
starDensity,
allStarsTwinkle,
twinkleProbability,
minTwinkleSpeed,
maxTwinkleSpeed,
generateStars,
]);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let animationFrameId: number;
const render = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
stars.forEach((star) => {
ctx.beginPath();
ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`;
ctx.fill();
if (star.twinkleSpeed !== null) {
star.opacity =
0.5 +
Math.abs(Math.sin((Date.now() * 0.001) / star.twinkleSpeed) * 0.5);
}
});
animationFrameId = requestAnimationFrame(render);
};
render();
return () => {
cancelAnimationFrame(animationFrameId);
};
}, [stars]);
return (
<canvas
ref={canvasRef}
className={cn("h-full w-full absolute inset-0", className)}
/>
);
};
Props
Shooting Stars
Prop | Type | Default | Description |
---|---|---|---|
minSpeed | number | 10 | Minimum speed of the shooting stars |
maxSpeed | number | 30 | Maximum speed of the shooting stars |
minDelay | number | 4200 | Minimum delay between shooting stars (in milliseconds) |
maxDelay | number | 8700 | Maximum delay between shooting stars (in milliseconds) |
starColor | string | "#9E00FF" | Color of the star (end of the gradient) |
trailColor | string | "#2EB9DF" | Color of the star's trail (start of the gradient) |
starWidth | number | 10 | Width of the shooting star |
starHeight | number | 1 | Height of the shooting star |
className | string | undefined | Additional CSS classes to apply to the SVG container |
Stars Background
Prop | Type | Default | Description |
---|---|---|---|
starDensity | number | 0.00015 | Determines the number of stars per pixel area. Higher values create more stars. |
allStarsTwinkle | boolean | true | If true, all stars will twinkle. If false, only a portion of stars will twinkle based on twinkleProbability. |
twinkleProbability | number | 0.7 | The probability (0-1) that a star will twinkle when allStarsTwinkle is false. |
minTwinkleSpeed | number | 0.5 | The minimum duration (in seconds) for a star's twinkle animation. |
maxTwinkleSpeed | number | 1 | The maximum duration (in seconds) for a star's twinkle animation. |
className | string | undefined | Additional CSS classes to apply to the container div. |
This component is created by Vijay Verma for figmaplug.in. You can checkout the original code here
Get beautiful, hand-crafted templates and components with Aceternity UI Pro
Professional, beautiful and elegant templates for your business. Get the best component packs and templates with Aceternity UI Pro.
The work that Manu did laid the foundation of online education that we provide today. The website he built for us is used by thousands of students every day. He took the requirements and built the ...
Jagvinder Kour
Chairperson at Golden Bells Academy