143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
|
|
interface Particle {
|
|
id: number;
|
|
x: number;
|
|
y: number;
|
|
size: number;
|
|
speedX: number;
|
|
speedY: number;
|
|
opacity: number;
|
|
color: string;
|
|
}
|
|
|
|
export const ParticleBackground: React.FC = () => {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
|
const particlesRef = useRef<Particle[]>([]);
|
|
const animationRef = useRef<number | undefined>(undefined);
|
|
const mouseRef = useRef({ x: 0, y: 0 });
|
|
|
|
useEffect(() => {
|
|
const handleResize = () => {
|
|
setDimensions({
|
|
width: window.innerWidth,
|
|
height: window.innerHeight
|
|
});
|
|
};
|
|
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
mouseRef.current = { x: e.clientX, y: e.clientY };
|
|
};
|
|
|
|
handleResize();
|
|
window.addEventListener('resize', handleResize);
|
|
window.addEventListener('mousemove', handleMouseMove);
|
|
|
|
return () => {
|
|
window.removeEventListener('resize', handleResize);
|
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
canvas.width = dimensions.width;
|
|
canvas.height = dimensions.height;
|
|
|
|
// Initialize particles
|
|
const particleCount = Math.floor((dimensions.width * dimensions.height) / 15000);
|
|
particlesRef.current = Array.from({ length: particleCount }, (_, i) => ({
|
|
id: i,
|
|
x: Math.random() * dimensions.width,
|
|
y: Math.random() * dimensions.height,
|
|
size: Math.random() * 2 + 0.5,
|
|
speedX: (Math.random() - 0.5) * 0.5,
|
|
speedY: (Math.random() - 0.5) * 0.5,
|
|
opacity: Math.random() * 0.5 + 0.2,
|
|
color: Math.random() > 0.7 ? '#FFAA00' : '#00D4FF'
|
|
}));
|
|
|
|
const animate = () => {
|
|
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
|
|
|
|
particlesRef.current.forEach((particle, index) => {
|
|
// Update position
|
|
particle.x += particle.speedX;
|
|
particle.y += particle.speedY;
|
|
|
|
// Mouse interaction - particles move away from cursor
|
|
const dx = mouseRef.current.x - particle.x;
|
|
const dy = mouseRef.current.y - particle.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distance < 100) {
|
|
const force = (100 - distance) / 100;
|
|
particle.x -= (dx / distance) * force * 2;
|
|
particle.y -= (dy / distance) * force * 2;
|
|
}
|
|
|
|
// Wrap around screen
|
|
if (particle.x < 0) particle.x = dimensions.width;
|
|
if (particle.x > dimensions.width) particle.x = 0;
|
|
if (particle.y < 0) particle.y = dimensions.height;
|
|
if (particle.y > dimensions.height) particle.y = 0;
|
|
|
|
// Draw particle
|
|
ctx.beginPath();
|
|
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
|
ctx.fillStyle = particle.color.replace(')', `, ${particle.opacity})`).replace('rgb', 'rgba').replace('#', '');
|
|
|
|
// Convert hex to rgba
|
|
const hex = particle.color;
|
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${particle.opacity})`;
|
|
ctx.fill();
|
|
|
|
// Draw connections between nearby particles
|
|
particlesRef.current.slice(index + 1).forEach(otherParticle => {
|
|
const dx = particle.x - otherParticle.x;
|
|
const dy = particle.y - otherParticle.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distance < 120) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(particle.x, particle.y);
|
|
ctx.lineTo(otherParticle.x, otherParticle.y);
|
|
ctx.strokeStyle = `rgba(0, 212, 255, ${0.1 * (1 - distance / 120)})`;
|
|
ctx.lineWidth = 0.5;
|
|
ctx.stroke();
|
|
}
|
|
});
|
|
});
|
|
|
|
animationRef.current = requestAnimationFrame(animate);
|
|
};
|
|
|
|
animate();
|
|
|
|
return () => {
|
|
if (animationRef.current) {
|
|
cancelAnimationFrame(animationRef.current);
|
|
}
|
|
};
|
|
}, [dimensions]);
|
|
|
|
return (
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="fixed inset-0 pointer-events-none z-0"
|
|
style={{ background: 'transparent' }}
|
|
/>
|
|
);
|
|
};
|