import React, { useRef, useEffect, useState } from "react"; import "./BackgroundCanvas.css"; const BackgroundCanvas = ({ theme = "light" }) => { const canvasRef = useRef(null); const animationRef = useRef(null); const jointsRef = useRef([]); const cameraRef = useRef(null); const keysRef = useRef(new Array(127).fill(0)); const worldRef = useRef({ width: 0, height: 0 }); const themeRef = useRef(theme); class Camera { constructor(position, zoom) { this.position = position; this.speed = 3; this.acceleration = { x: 0, y: 0, z: 0 }; this.zoom = zoom; } } class Joint { constructor(position, vector) { this.position = position; this.vector = vector; this.speed = Math.random() * 1.5 + 0.5; this.bone_length = 150; const shapes = ["circle", "triangle", "square", "pentagon"]; this.shape = shapes[Math.floor(Math.random() * shapes.length)]; this.size = Math.random() * 4 + 3; this.angle = 0; this.angularSpeed = (Math.random() - 0.5) * 0.1; this.pulse = Math.random() * Math.PI * 2; } } const generateJoints = (numb, worldWidth, worldHeight) => { const arr = new Array(numb).fill(0); arr.forEach((v, i) => { arr[i] = new Joint( { x: Math.random() * worldWidth, y: Math.random() * worldHeight, }, { x: Math.random() * 2 - 1, y: Math.random() * 2 - 1, }, ); }); return arr; }; const moveJoints = (joints, world) => { joints.forEach((joint) => { joint.position.x += joint.vector.x * joint.speed; joint.position.y += joint.vector.y * joint.speed; joint.angle += joint.angularSpeed; joint.pulse += 0.05; if (joint.position.x < 0) joint.position.x = world.width; if (joint.position.x > world.width) joint.position.x = 0; if (joint.position.y < 0) joint.position.y = world.height; if (joint.position.y > world.height) joint.position.y = 0; }); }; const drawJoints = (ctx, joints, camera, view, theme) => { const len = joints.length; let lineColor, pointColor, backgroundColor; if (theme === "dark") { backgroundColor = "#313131"; lineColor = "#F5EEE6"; pointColor = "#e06923"; } else { backgroundColor = "#f1f1f1ff"; lineColor = "rgba(49, 49, 49, 0.3)"; // More subtle lines pointColor = "#041c40"; } ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, view.width, view.height); for (let i = 0; i < len; i++) { for (let j = i + 1; j < len; j += 3) { const length = Math.hypot( joints[j].position.x - joints[i].position.x, joints[j].position.y - joints[i].position.y, ); if (length <= joints[i].bone_length) { ctx.beginPath(); ctx.strokeStyle = lineColor; ctx.lineWidth = camera.zoom * (30 / length); ctx.moveTo( view.width / 2 + (joints[i].position.x - camera.position.x) * camera.zoom, view.height / 2 + (joints[i].position.y - camera.position.y) * camera.zoom, ); ctx.lineTo( view.width / 2 + (joints[j].position.x - camera.position.x) * camera.zoom, view.height / 2 + (joints[j].position.y - camera.position.y) * camera.zoom, ); ctx.stroke(); ctx.closePath(); } } const currentSize = (joints[i].size + Math.sin(joints[i].pulse) * 1.5) * camera.zoom; ctx.save(); ctx.translate( view.width / 2 + (joints[i].position.x - camera.position.x) * camera.zoom, view.height / 2 + (joints[i].position.y - camera.position.y) * camera.zoom, ); ctx.rotate(joints[i].angle); ctx.fillStyle = pointColor; ctx.beginPath(); if (joints[i].shape === "circle") { ctx.arc(0, 0, currentSize, 0, Math.PI * 2); } else if (joints[i].shape === "triangle") { ctx.moveTo(0, -currentSize); ctx.lineTo(currentSize, currentSize); ctx.lineTo(-currentSize, currentSize); } else if (joints[i].shape === "square") { ctx.rect(-currentSize / 2, -currentSize / 2, currentSize, currentSize); } else if (joints[i].shape === "pentagon") { for (let k = 0; k < 5; k++) { ctx.lineTo( currentSize * Math.cos((k * 2 * Math.PI) / 5 - Math.PI / 2), currentSize * Math.sin((k * 2 * Math.PI) / 5 - Math.PI / 2), ); } } ctx.fill(); ctx.closePath(); ctx.restore(); } }; const moveCamera = (camera, keys) => { if (keys[37]) camera.acceleration.x -= camera.speed; if (keys[38]) camera.acceleration.y -= camera.speed; if (keys[39]) camera.acceleration.x += camera.speed; if (keys[40]) camera.acceleration.y += camera.speed; if (keys[188]) camera.acceleration.z += 0.003; if (keys[190]) camera.acceleration.z -= 0.003; camera.position.x += camera.acceleration.x; camera.position.y += camera.acceleration.y; camera.zoom += camera.acceleration.z; camera.acceleration.x *= 0.96; camera.acceleration.y *= 0.96; camera.acceleration.z *= 0.9; }; const initCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); const view = { width: window.innerWidth, height: window.innerHeight, }; canvas.width = view.width; canvas.height = view.height; worldRef.current = { width: view.width * 2, height: view.height * 2, }; cameraRef.current = new Camera( { x: worldRef.current.width / 2, y: worldRef.current.height / 2, }, 1, ); const isMobile = window.innerWidth < 768; const numberOfJoints = isMobile ? 150 : 600; jointsRef.current = generateJoints( numberOfJoints, worldRef.current.width, worldRef.current.height, ); if (isMobile) { jointsRef.current.forEach((joint) => { joint.bone_length = 120; joint.speed = 0.4; }); } }; const animate = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); const view = { width: canvas.width, height: canvas.height, }; moveCamera(cameraRef.current, keysRef.current); moveJoints(jointsRef.current, worldRef.current); drawJoints( ctx, jointsRef.current, cameraRef.current, view, themeRef.current, ); animationRef.current = requestAnimationFrame(animate); }; useEffect(() => { themeRef.current = theme; }, [theme]); useEffect(() => { const handleKeyDown = (e) => { keysRef.current[e.keyCode] = 1; }; const handleKeyUp = (e) => { keysRef.current[e.keyCode] = 0; }; window.addEventListener("keydown", handleKeyDown); window.addEventListener("keyup", handleKeyUp); return () => { window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keyup", handleKeyUp); }; }, []); useEffect(() => { const handleResize = () => { const canvas = canvasRef.current; if (!canvas) return; canvas.width = window.innerWidth; canvas.height = window.innerHeight; worldRef.current = { width: canvas.width * 2, height: canvas.height * 2, }; }; window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); useEffect(() => { initCanvas(); animate(); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, []); return (