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); }; } }; }