|
|
import React, { useEffect, useRef } from 'react'; |
|
|
|
|
|
const BackgroundAnimation: React.FC = () => { |
|
|
const canvasRef = useRef<HTMLCanvasElement>(null); |
|
|
|
|
|
useEffect(() => { |
|
|
const canvas = canvasRef.current; |
|
|
if (!canvas) return; |
|
|
|
|
|
const ctx = canvas.getContext('2d'); |
|
|
if (!ctx) return; |
|
|
|
|
|
let width = window.innerWidth; |
|
|
let height = window.innerHeight; |
|
|
canvas.width = width; |
|
|
canvas.height = height; |
|
|
|
|
|
|
|
|
const numStars = 400; |
|
|
const speed = 2; |
|
|
const stars: { x: number; y: number; z: number; o: number }[] = []; |
|
|
|
|
|
|
|
|
for (let i = 0; i < numStars; i++) { |
|
|
stars.push({ |
|
|
x: Math.random() * width - width / 2, |
|
|
y: Math.random() * height - height / 2, |
|
|
z: Math.random() * width, |
|
|
o: Math.random(), |
|
|
}); |
|
|
} |
|
|
|
|
|
const animate = () => { |
|
|
|
|
|
ctx.fillStyle = '#020617'; |
|
|
ctx.fillRect(0, 0, width, height); |
|
|
|
|
|
const cx = width / 2; |
|
|
const cy = height / 2; |
|
|
|
|
|
stars.forEach((star) => { |
|
|
|
|
|
star.z -= speed; |
|
|
|
|
|
|
|
|
if (star.z <= 0) { |
|
|
star.z = width; |
|
|
star.x = Math.random() * width - width / 2; |
|
|
star.y = Math.random() * height - height / 2; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const x = cx + (star.x / star.z) * width; |
|
|
const y = cy + (star.y / star.z) * width; |
|
|
|
|
|
|
|
|
const size = (1 - star.z / width) * 3; |
|
|
|
|
|
|
|
|
const opacity = (1 - star.z / width); |
|
|
|
|
|
|
|
|
if (x >= 0 && x <= width && y >= 0 && y <= height) { |
|
|
ctx.beginPath(); |
|
|
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; |
|
|
ctx.arc(x, y, size, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} |
|
|
}); |
|
|
|
|
|
requestAnimationFrame(animate); |
|
|
}; |
|
|
|
|
|
const animationId = requestAnimationFrame(animate); |
|
|
|
|
|
const handleResize = () => { |
|
|
width = window.innerWidth; |
|
|
height = window.innerHeight; |
|
|
canvas.width = width; |
|
|
canvas.height = height; |
|
|
}; |
|
|
|
|
|
window.addEventListener('resize', handleResize); |
|
|
|
|
|
return () => { |
|
|
cancelAnimationFrame(animationId); |
|
|
window.removeEventListener('resize', handleResize); |
|
|
}; |
|
|
}, []); |
|
|
|
|
|
return ( |
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0 bg-slate-950"> |
|
|
<canvas ref={canvasRef} className="absolute inset-0" /> |
|
|
|
|
|
{/* Subtle Nebula Overlay for atmosphere */} |
|
|
<div className="absolute top-0 left-1/4 w-[600px] h-[600px] bg-blue-600/10 rounded-full mix-blend-screen filter blur-[120px] opacity-30 animate-blob" /> |
|
|
<div className="absolute bottom-0 right-1/4 w-[600px] h-[600px] bg-purple-600/10 rounded-full mix-blend-screen filter blur-[120px] opacity-30 animate-blob animation-delay-2000" /> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default BackgroundAnimation; |
|
|
|