Sparkles
A sparkle animation.
Made by lucasimport Sparkles from "@/components/targetblank/animations/sparkles";
import { Button } from "@/components/ui/button";
import { useTheme } from "next-themes";
export const SparklesDemo = () => {
const { theme } = useTheme();
return (
<div className="w-fit h-full flex items-center justify-center mx-auto">
<Sparkles sparkColor={theme === "dark" ? "#FFF" : "#000"}>
<Button variant="outline">Click me</Button>
</Sparkles>
</div>
);
};
Installation
Install the following dependencies:
Copy and paste the following code into your project:
import * as React from "react";
export enum Easing {
LINEAR = "linear",
EASE_IN = "ease-in",
EASE_OUT = "ease-out",
EASE_IN_OUT = "ease-in-out",
}
interface SparklesProps extends React.HTMLAttributes<HTMLDivElement> {
sparkColor?: string;
sparkSize?: number;
sparkRadius?: number;
sparkCount?: number;
duration?: number;
easing?: Easing;
extraScale?: number;
children?: React.ReactNode;
}
interface Spark {
x: number;
y: number;
angle: number;
startTime: number;
}
const Sparkles: React.FC<SparklesProps> = ({
sparkColor = "#fff",
sparkSize = 10,
sparkRadius = 15,
sparkCount = 8,
duration = 400,
easing = Easing.EASE_OUT,
extraScale = 1.0,
children,
...props
}) => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const sparksRef = React.useRef<Spark[]>([]);
const startTimeRef = React.useRef<number | null>(null);
React.useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const parent = canvas.parentElement;
if (!parent) return;
let resizeTimeout: NodeJS.Timeout;
const resizeCanvas = () => {
const { width, height } = parent.getBoundingClientRect();
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
};
const handleResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(resizeCanvas, 100);
};
const ro = new ResizeObserver(handleResize);
ro.observe(parent);
resizeCanvas();
return () => {
ro.disconnect();
clearTimeout(resizeTimeout);
};
}, []);
const easeFunc = React.useCallback(
(t: number) => {
switch (easing) {
case Easing.LINEAR:
return t;
case Easing.EASE_IN:
return t * t;
case Easing.EASE_IN_OUT:
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
default:
return t * (2 - t);
}
},
[easing],
);
React.useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let animationId: number;
const draw = (timestamp: number) => {
if (!startTimeRef.current) {
startTimeRef.current = timestamp;
}
ctx?.clearRect(0, 0, canvas.width, canvas.height);
sparksRef.current = sparksRef.current.filter((spark: Spark) => {
const elapsed = timestamp - spark.startTime;
if (elapsed >= duration) {
return false;
}
const progress = elapsed / duration;
const eased = easeFunc(progress);
const distance = eased * sparkRadius * extraScale;
const lineLength = sparkSize * (1 - eased);
const x1 = spark.x + distance * Math.cos(spark.angle);
const y1 = spark.y + distance * Math.sin(spark.angle);
const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);
const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);
ctx.strokeStyle = sparkColor;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
return true;
});
animationId = requestAnimationFrame(draw);
};
animationId = requestAnimationFrame(draw);
return () => {
cancelAnimationFrame(animationId);
};
}, [
sparkColor,
sparkSize,
sparkRadius,
sparkCount,
duration,
easeFunc,
extraScale,
]);
const handleClick = (e: React.MouseEvent<HTMLDivElement>): void => {
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const now = performance.now();
const newSparks: Spark[] = Array.from({ length: sparkCount }, (_, i) => ({
x,
y,
angle: (2 * Math.PI * i) / sparkCount,
startTime: now,
}));
sparksRef.current.push(...newSparks);
};
return (
<div className="relative w-full h-full" onClick={handleClick} {...props}>
<canvas
ref={canvasRef}
className="absolute inset-0 pointer-events-none w-full h-full"
/>
{children}
</div>
);
};
export default Sparkles;
Update the import paths to match your project setup.
Usage
<Sparkles />
Props
Prop | Type | Default |
---|---|---|
animation? | boolean | true |
color? | string[] | ['#000'] |
containerClassName? | string | default |
containerHeight? | number | 40 |
containerWidth? | string | 100% |
delay? | number | 40 |
direction? | string | right |
duration? | object | { min: 1.2, max: 1.8 } |
firstChild? | React.ReactNode | - |
maxCurve? | number | 18 |
opacity? | number | 1 |
secondChild? | React.ReactNode | - |
size? | object | { min: 6, max: 12 } |
speed? | object | { min: 0, max: 1.2 } |
Credits
- Credits to Virgil Pana for the button style.