interface Props {
// Color of the first set of particles (default: Ultramarine)
primaryColor?: string;
// Color of the second set of particles (default: Aqua)
secondaryColor?: string;
// Radius of mouse interaction (default: 150px)
mouseRadius?: number;
// Background color or gradient (default: Dark gradient)
backgroundColor?: string;
}
export default function ParticleBackground({
primaryColor = "rgba(68, 116, 255, 0.8)", // Ultramarine
secondaryColor = "rgba(66, 249, 255, 0.8)", // Aqua
mouseRadius = 150,
backgroundColor = "linear-gradient(to bottom, rgba(0,0,0,0.95), rgba(0,0,0,1))"
}: Props) {
return {
canvas: document.createElement("canvas"),
mount(canvas: HTMLCanvasElement) {
// Set up canvas styles
canvas.style.position = "fixed";
canvas.style.top = "0";
canvas.style.left = "0";
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.zIndex = "-1";
canvas.style.background = backgroundColor;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Initialize variables
let particles: Array<{
x: number;
y: number;
vx: number;
vy: number;
targetX: number;
targetY: number;
radius: number;
color: string;
}> = [];
const mouse = { x: 0, y: 0, radius: mouseRadius };
let animationFrame: number;
// Update canvas size
function updateCanvasSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
// Generate points in a circle pattern
function generatePoints() {
const points: [number, number][] = [];
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const size = Math.min(canvas.width, canvas.height) * 0.2;
const gridSize = 15;
for (let x = 0; x < canvas.width; x += gridSize) {
for (let y = 0; y < canvas.height; y += gridSize) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > size) {
points.push([x, y]);
}
}
}
return points;
}
// Initialize particles
function initParticles() {
const points = generatePoints();
particles = points.map(([targetX, targetY]) => {
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * 200;
return {
x: targetX + Math.cos(angle) * distance,
y: targetY + Math.sin(angle) * distance,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
targetX,
targetY,
radius: Math.random() * 1.5 + 1,
color: Math.random() < 0.5 ? primaryColor : secondaryColor
};
});
}
// Animation loop
function animate() {
if (!ctx) return;
ctx.fillStyle = "rgba(0, 0, 0, 0.2)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
// Mouse repulsion
const dx = mouse.x - particle.x;
const dy = mouse.y - particle.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < mouse.radius) {
const force = (mouse.radius - distance) / mouse.radius;
const angle = Math.atan2(dy, dx);
particle.vx -= Math.cos(angle) * force * 2;
particle.vy -= Math.sin(angle) * force * 2;
}
// Target attraction
const tdx = particle.targetX - particle.x;
const tdy = particle.targetY - particle.y;
const targetDistance = Math.sqrt(tdx * tdx + tdy * tdy);
if (targetDistance > 1) {
particle.vx += (tdx / targetDistance) * 0.2;
particle.vy += (tdy / targetDistance) * 0.2;
}
// Update position
particle.x += particle.vx;
particle.y += particle.vy;
// Apply drag
particle.vx *= 0.95;
particle.vy *= 0.95;
// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
});
animationFrame = requestAnimationFrame(animate);
}
// Event handlers
function handleMouseMove(e: MouseEvent) {
const rect = canvas.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
}
// Initialize
updateCanvasSize();
initParticles();
animate();
// Add event listeners
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("resize", () => {
updateCanvasSize();
initParticles();
});
// Cleanup function
return () => {
cancelAnimationFrame(animationFrame);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("resize", updateCanvasSize);
};
}
};
}