import { useEffect, useRef } from 'react'; import type { Theme } from '../context/ThemeContext'; interface Orb { x: number; y: number; r: number; vx: number; vy: number; color: string; glowAlpha: number; pulsePhase: number; pulseSpeed: number; } export default function StarBackground({ theme = 'dark' }: { theme?: Theme }) { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const resize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; resize(); window.addEventListener('resize', resize); // 生成星星 const stars = Array.from({ length: 220 }, () => ({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, r: Math.random() * 1.2 + 0.2, opacity: Math.random() * 0.6 + 0.2, speed: Math.random() * 0.015 + 0.005, phase: Math.random() * Math.PI * 2, })); // 深空云雾粒子 const nebula = Array.from({ length: 6 }, (_, i) => ({ x: (i / 6) * canvas.width + Math.random() * 200 - 100, y: Math.random() * canvas.height, r: Math.random() * 180 + 120, color: ['rgba(0,80,160,', 'rgba(40,0,120,', 'rgba(0,60,100,', 'rgba(20,0,80,', 'rgba(0,100,120,', 'rgba(60,0,100,'][i], })); // 大小球体 — 飞来飞去的光球 const darkOrbColors = [ 'rgba(220,240,255,', 'rgba(200,230,255,', 'rgba(255,255,255,', 'rgba(230,240,255,', 'rgba(0,180,255,', 'rgba(120,100,255,', 'rgba(0,229,153,', 'rgba(0,212,255,', ]; const lightOrbColors = [ 'rgba(160,185,230,', 'rgba(180,200,240,', 'rgba(200,215,245,', 'rgba(140,170,220,', 'rgba(100,140,210,', 'rgba(170,190,235,', 'rgba(120,160,220,', 'rgba(190,210,245,', ]; const orbColors = theme === 'dark' ? darkOrbColors : lightOrbColors; const orbGlowBase = theme === 'dark' ? 0.025 : 0.07; const orbs: Orb[] = Array.from({ length: 64 }, (_, i) => ({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, r: i < 24 ? (Math.random() * 30 + 35) : (Math.random() * 12 + 10), vx: (Math.random() - 0.5) * 2.2, vy: (Math.random() - 0.5) * 1.6, color: orbColors[i % orbColors.length], glowAlpha: Math.random() * orbGlowBase + orbGlowBase * 0.4, pulsePhase: Math.random() * Math.PI * 2, pulseSpeed: Math.random() * 0.012 + 0.006, })); let frame = 0; let animId: number; const draw = () => { frame++; const W = canvas.width; const H = canvas.height; ctx.clearRect(0, 0, W, H); // 背景渐变 const grad = ctx.createRadialGradient(W * 0.5, H * 0.5, 0, W * 0.5, H * 0.5, W * 0.7); if (theme === 'dark') { grad.addColorStop(0, 'rgba(0,15,40,1)'); grad.addColorStop(0.5, 'rgba(0,8,25,1)'); grad.addColorStop(1, 'rgba(0,4,15,1)'); } else { grad.addColorStop(0, 'rgba(248,250,255,1)'); grad.addColorStop(0.5, 'rgba(243,246,255,1)'); grad.addColorStop(1, 'rgba(236,241,255,1)'); } ctx.fillStyle = grad; ctx.fillRect(0, 0, W, H); // 星云雾 nebula.forEach(n => { const g = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r); const nebulaAlpha = theme === 'dark' ? '0.06' : '0.04'; g.addColorStop(0, n.color + nebulaAlpha + ')'); g.addColorStop(1, n.color + '0)'); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx.fill(); }); // ★ 飞行球体 orbs.forEach(orb => { // 更新位置 orb.x += orb.vx; orb.y += orb.vy; // 边界反弹(加上球体半径,让球不会从中心出界) if (orb.x < -orb.r) orb.x = W + orb.r; if (orb.x > W + orb.r) orb.x = -orb.r; if (orb.y < -orb.r) orb.y = H + orb.r; if (orb.y > H + orb.r) orb.y = -orb.r; // 脉冲效果 const pulse = 0.7 + 0.3 * Math.sin(frame * orb.pulseSpeed + orb.pulsePhase); const alpha = orb.glowAlpha * pulse; // 外层大光晕 const outerR = orb.r * 1.5; const outerGrad = ctx.createRadialGradient(orb.x, orb.y, 0, orb.x, orb.y, outerR); outerGrad.addColorStop(0, orb.color + (alpha * 0.9).toFixed(3) + ')'); outerGrad.addColorStop(0.5, orb.color + (alpha * 0.3).toFixed(3) + ')'); outerGrad.addColorStop(1, orb.color + '0)'); ctx.fillStyle = outerGrad; ctx.beginPath(); ctx.arc(orb.x, orb.y, outerR, 0, Math.PI * 2); ctx.fill(); // 内层球体 const innerGrad = ctx.createRadialGradient( orb.x - orb.r * 0.2, orb.y - orb.r * 0.2, 0, orb.x, orb.y, orb.r ); innerGrad.addColorStop(0, orb.color + (alpha * 2.0).toFixed(3) + ')'); innerGrad.addColorStop(0.6, orb.color + (alpha * 0.8).toFixed(3) + ')'); innerGrad.addColorStop(1, orb.color + '0)'); ctx.fillStyle = innerGrad; ctx.beginPath(); ctx.arc(orb.x, orb.y, orb.r, 0, Math.PI * 2); ctx.fill(); // 高光点 const hlR = orb.r * 0.25; const hlGrad = ctx.createRadialGradient( orb.x - orb.r * 0.25, orb.y - orb.r * 0.25, 0, orb.x - orb.r * 0.25, orb.y - orb.r * 0.25, hlR ); hlGrad.addColorStop(0, 'rgba(255,255,255,' + (alpha * 4).toFixed(3) + ')'); hlGrad.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = hlGrad; ctx.beginPath(); ctx.arc(orb.x - orb.r * 0.25, orb.y - orb.r * 0.25, hlR, 0, Math.PI * 2); ctx.fill(); }); // 星星 stars.forEach(s => { const twinkle = 0.3 + 0.7 * (0.5 + 0.5 * Math.sin(frame * s.speed + s.phase)); ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fillStyle = theme === 'dark' ? `rgba(180,220,255,${s.opacity * twinkle})` : `rgba(100,140,200,${s.opacity * twinkle * 0.5})`; ctx.fill(); // 亮星加光晕 if (s.r > 1.0) { const halo = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.r * 4); halo.addColorStop(0, theme === 'dark' ? `rgba(100,200,255,${0.2 * twinkle})` : `rgba(80,130,220,${0.08 * twinkle})`); halo.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(s.x, s.y, s.r * 4, 0, Math.PI * 2); ctx.fill(); } }); // 水平光线 if (frame % 300 === 0) { const y = Math.random() * H; const lineGrad = ctx.createLinearGradient(0, y, W, y); lineGrad.addColorStop(0, 'rgba(0,212,255,0)'); lineGrad.addColorStop(0.5, 'rgba(0,212,255,0.06)'); lineGrad.addColorStop(1, 'rgba(0,212,255,0)'); ctx.strokeStyle = lineGrad; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); } animId = requestAnimationFrame(draw); }; draw(); return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize); }; }, [theme]); return ( ); }