"use client"; // 回响星核 / Echo Nexus — 深空星空背景 Canvas import { useEffect, useRef } from "react"; interface Star { x: number; y: number; z: number; // 深度 0-1 r: number; tw: number; // 闪烁相位 hue: number; } interface Dust { x: number; y: number; vx: number; vy: number; life: number; maxLife: number; color: string; } export function StarfieldCanvas({ className }: { className?: string }) { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; let w = 0; let h = 0; let dpr = 1; let stars: Star[] = []; const dust: Dust[] = []; let raf = 0; let lastT = performance.now(); const palette = ["#34d399", "#fb7185", "#fbbf24", "#e879f9", "#a5f3fc"]; function resize() { dpr = Math.min(2, window.devicePixelRatio || 1); w = canvas!.clientWidth; h = canvas!.clientHeight; canvas!.width = Math.floor(w * dpr); canvas!.height = Math.floor(h * dpr); ctx!.setTransform(dpr, 0, 0, dpr, 0, 0); // 生成星星:密度随面积 const count = Math.min(220, Math.floor((w * h) / 7000)); stars = Array.from({ length: count }, () => ({ x: Math.random() * w, y: Math.random() * h, z: Math.random(), r: Math.random() * 1.4 + 0.2, tw: Math.random() * Math.PI * 2, hue: Math.random() < 0.15 ? Math.floor(Math.random() * palette.length) : -1, })); } resize(); const ro = new ResizeObserver(resize); ro.observe(canvas); function spawnDust() { if (dust.length > 60) return; const color = palette[Math.floor(Math.random() * palette.length)]; dust.push({ x: Math.random() * w, y: h + 10, vx: (Math.random() - 0.5) * 0.3, vy: -(Math.random() * 0.5 + 0.2), life: 0, maxLife: 600 + Math.random() * 800, color, }); } function draw(t: number) { const dt = t - lastT; lastT = t; // 背景渐变 const g = ctx!.createRadialGradient(w * 0.5, h * 0.4, 0, w * 0.5, h * 0.5, Math.max(w, h) * 0.8); g.addColorStop(0, "#1a0f2e"); g.addColorStop(0.45, "#0d0a1f"); g.addColorStop(1, "#050410"); ctx!.fillStyle = g; ctx!.fillRect(0, 0, w, h); // 星云团块 ctx!.globalCompositeOperation = "screen"; for (let i = 0; i < 3; i++) { const cx = w * (0.2 + i * 0.3) + Math.sin(t / 9000 + i) * 40; const cy = h * (0.3 + (i % 2) * 0.4) + Math.cos(t / 11000 + i) * 30; const rad = Math.max(80, Math.min(w, h) * (0.25 + i * 0.05)); const ng = ctx!.createRadialGradient(cx, cy, 0, cx, cy, rad); const col = i === 0 ? "rgba(232,121,249,0.10)" : i === 1 ? "rgba(52,211,153,0.08)" : "rgba(251,113,133,0.08)"; ng.addColorStop(0, col); ng.addColorStop(1, "rgba(0,0,0,0)"); ctx!.fillStyle = ng; ctx!.beginPath(); ctx!.arc(cx, cy, rad, 0, Math.PI * 2); ctx!.fill(); } ctx!.globalCompositeOperation = "source-over"; // 星星 for (const s of stars) { s.tw += 0.02 + s.z * 0.04; const alpha = 0.4 + Math.sin(s.tw) * 0.35 + s.z * 0.3; if (s.hue >= 0) { ctx!.fillStyle = palette[s.hue]; ctx!.globalAlpha = Math.max(0, Math.min(1, alpha)); ctx!.beginPath(); ctx!.arc(s.x, s.y, s.r * 1.6, 0, Math.PI * 2); ctx!.fill(); // 光晕 ctx!.globalAlpha = Math.max(0, Math.min(1, alpha * 0.25)); ctx!.beginPath(); ctx!.arc(s.x, s.y, s.r * 4, 0, Math.PI * 2); ctx!.fill(); } else { ctx!.fillStyle = `rgba(220,230,255,${Math.max(0, Math.min(1, alpha))})`; ctx!.beginPath(); ctx!.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx!.fill(); } // 缓慢漂移 s.y += 0.02 + s.z * 0.05; if (s.y > h + 2) { s.y = -2; s.x = Math.random() * w; } } ctx!.globalAlpha = 1; // 偶尔生成星尘 if (Math.random() < 0.04) spawnDust(); // 星尘 ctx!.globalCompositeOperation = "screen"; for (let i = dust.length - 1; i >= 0; i--) { const d = dust[i]; d.life += dt; d.x += d.vx * dt * 0.06; d.y += d.vy * dt * 0.06; if (d.life > d.maxLife || d.y < -10) { dust.splice(i, 1); continue; } const a = Math.sin((d.life / d.maxLife) * Math.PI) * 0.7; ctx!.fillStyle = d.color; ctx!.globalAlpha = a; ctx!.beginPath(); ctx!.arc(d.x, d.y, 1.6, 0, Math.PI * 2); ctx!.fill(); ctx!.globalAlpha = a * 0.2; ctx!.beginPath(); ctx!.arc(d.x, d.y, 5, 0, Math.PI * 2); ctx!.fill(); } ctx!.globalAlpha = 1; ctx!.globalCompositeOperation = "source-over"; raf = requestAnimationFrame(draw); } raf = requestAnimationFrame(draw); return () => { cancelAnimationFrame(raf); ro.disconnect(); }; }, []); return (