lingjing/components/Starfield.jsx

79 lines
2.1 KiB
JavaScript

'use client';
import { useEffect, useRef } from 'react';
export default function Starfield({ className = '', animated = false }) {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let raf = null;
let width = 0;
let height = 0;
let stars = [];
const createStars = () => {
const count = Math.max(90, Math.floor((width * height) / 16000));
stars = Array.from({ length: count }, () => ({
x: Math.random() * width,
y: Math.random() * height,
r: Math.random() * 1.4 + 0.4,
a: Math.random() * 0.7 + 0.2,
v: Math.random() * 0.08 + 0.02,
}));
};
const resize = () => {
const dpr = window.devicePixelRatio || 1;
width = canvas.clientWidth;
height = canvas.clientHeight;
canvas.width = Math.floor(width * dpr);
canvas.height = Math.floor(height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
createStars();
draw();
};
const draw = () => {
ctx.clearRect(0, 0, width, height);
const bg = ctx.createLinearGradient(0, 0, 0, height);
bg.addColorStop(0, '#0b0720');
bg.addColorStop(1, '#05030f');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
for (const s of stars) {
ctx.beginPath();
ctx.fillStyle = `rgba(220,225,255,${s.a})`;
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fill();
}
};
const tick = () => {
for (const s of stars) {
s.a += (Math.random() - 0.5) * s.v;
if (s.a < 0.15) s.a = 0.15;
if (s.a > 0.95) s.a = 0.95;
}
draw();
raf = window.requestAnimationFrame(tick);
};
resize();
window.addEventListener('resize', resize);
if (animated) raf = window.requestAnimationFrame(tick);
return () => {
window.removeEventListener('resize', resize);
if (raf) window.cancelAnimationFrame(raf);
};
}, [animated]);
return <canvas ref={canvasRef} className={className} aria-hidden="true" />;
}