114 lines
3.4 KiB
JavaScript
114 lines
3.4 KiB
JavaScript
|
|
"use client";
|
||
|
|
import { useEffect, useRef } from "react";
|
||
|
|
|
||
|
|
export default function InteractiveBackground() {
|
||
|
|
const canvasRef = useRef(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const canvas = canvasRef.current;
|
||
|
|
if (!canvas) return;
|
||
|
|
const ctx = canvas.getContext("2d");
|
||
|
|
|
||
|
|
let animationFrameId;
|
||
|
|
let width = (canvas.width = window.innerWidth);
|
||
|
|
let height = (canvas.height = window.innerHeight);
|
||
|
|
|
||
|
|
// تتبع حركة الماوس
|
||
|
|
const mouse = { x: null, y: null, radius: 180 };
|
||
|
|
|
||
|
|
const handleMouseMove = (e) => {
|
||
|
|
mouse.x = e.clientX;
|
||
|
|
mouse.y = e.clientY;
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleMouseLeave = () => {
|
||
|
|
mouse.x = null;
|
||
|
|
mouse.y = null;
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleResize = () => {
|
||
|
|
width = canvas.width = window.innerWidth;
|
||
|
|
height = canvas.height = window.innerHeight;
|
||
|
|
};
|
||
|
|
|
||
|
|
window.addEventListener("mousemove", handleMouseMove);
|
||
|
|
window.addEventListener("mouseleave", handleMouseLeave);
|
||
|
|
window.addEventListener("resize", handleResize);
|
||
|
|
|
||
|
|
const particles = Array.from({ length: 70 }, () => ({
|
||
|
|
x: Math.random() * width,
|
||
|
|
y: Math.random() * height,
|
||
|
|
vx: (Math.random() - 0.5) * 0.8,
|
||
|
|
vy: (Math.random() - 0.5) * 0.8,
|
||
|
|
radius: Math.random() * 2 + 1,
|
||
|
|
}));
|
||
|
|
|
||
|
|
const draw = () => {
|
||
|
|
ctx.clearRect(0, 0, width, height);
|
||
|
|
|
||
|
|
particles.forEach((p, i) => {
|
||
|
|
p.x += p.vx;
|
||
|
|
p.y += p.vy;
|
||
|
|
|
||
|
|
if (p.x < 0 || p.x > width) p.vx *= -1;
|
||
|
|
if (p.y < 0 || p.y > height) p.vy *= -1;
|
||
|
|
|
||
|
|
|
||
|
|
if (mouse.x !== null && mouse.y !== null) {
|
||
|
|
const dx = mouse.x - p.x;
|
||
|
|
const dy = mouse.y - p.y;
|
||
|
|
const dist = Math.hypot(dx, dy);
|
||
|
|
|
||
|
|
if (dist < mouse.radius) {
|
||
|
|
const angle = Math.atan2(dy, dx);
|
||
|
|
const force = (mouse.radius - dist) / mouse.radius;
|
||
|
|
p.x -= Math.cos(angle) * force * 2;
|
||
|
|
p.y -= Math.sin(angle) * force * 2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
|
||
|
|
ctx.fillStyle = "rgba(99, 102, 241, 0.7)";
|
||
|
|
ctx.fill();
|
||
|
|
|
||
|
|
|
||
|
|
for (let j = i + 1; j < particles.length; j++) {
|
||
|
|
const p2 = particles[j];
|
||
|
|
const dist = Math.hypot(p.x - p2.x, p.y - p2.y);
|
||
|
|
if (dist < 140) {
|
||
|
|
const opacity = 1 - dist / 140;
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.moveTo(p.x, p.y);
|
||
|
|
ctx.lineTo(p2.x, p2.y);
|
||
|
|
ctx.strokeStyle = `rgba(99, 102, 241, ${opacity * 0.25})`;
|
||
|
|
ctx.lineWidth = 1;
|
||
|
|
ctx.stroke();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
animationFrameId = requestAnimationFrame(draw);
|
||
|
|
};
|
||
|
|
|
||
|
|
draw();
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
window.removeEventListener("mousemove", handleMouseMove);
|
||
|
|
window.removeEventListener("mouseleave", handleMouseLeave);
|
||
|
|
window.removeEventListener("resize", handleResize);
|
||
|
|
cancelAnimationFrame(animationFrameId);
|
||
|
|
};
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 pointer-events-none z-0 bg-slate-950 overflow-hidden">
|
||
|
|
<div className="absolute top-1/4 -left-20 w-96 h-96 bg-indigo-600/20 rounded-full blur-3xl animate-pulse" />
|
||
|
|
<div className="absolute bottom-1/4 -right-20 w-96 h-96 bg-blue-600/20 rounded-full blur-3xl animate-pulse delay-1000" />
|
||
|
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-3xl" />
|
||
|
|
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|