93 lines
3.4 KiB
React
93 lines
3.4 KiB
React
import { useEffect, useRef } from 'react';
|
|
|
|
export default function Background() {
|
|
const canvasRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext('2d');
|
|
let raf;
|
|
let w, h, dpr;
|
|
const stars = [];
|
|
const drift = [];
|
|
|
|
const resize = () => {
|
|
dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
w = canvas.width = window.innerWidth * dpr;
|
|
h = canvas.height = window.innerHeight * dpr;
|
|
canvas.style.width = window.innerWidth + 'px';
|
|
canvas.style.height = window.innerHeight + 'px';
|
|
stars.length = 0;
|
|
const density = Math.min(260, Math.floor((window.innerWidth * window.innerHeight) / 9000));
|
|
for (let i = 0; i < density; i++) {
|
|
stars.push({
|
|
x: Math.random() * w,
|
|
y: Math.random() * h,
|
|
r: Math.random() * 1.3 * dpr + 0.3 * dpr,
|
|
baseAlpha: Math.random() * 0.6 + 0.2,
|
|
twinkleSpeed: Math.random() * 0.003 + 0.0005,
|
|
twinklePhase: Math.random() * Math.PI * 2,
|
|
hue: Math.random() < 0.2 ? 'moon' : 'white',
|
|
});
|
|
}
|
|
drift.length = 0;
|
|
for (let i = 0; i < 18; i++) {
|
|
drift.push({
|
|
x: Math.random() * w,
|
|
y: Math.random() * h,
|
|
vx: (Math.random() - 0.5) * 0.15,
|
|
vy: (Math.random() - 0.5) * 0.1 - 0.02,
|
|
r: Math.random() * 2 * dpr + 0.5 * dpr,
|
|
alpha: Math.random() * 0.3 + 0.05,
|
|
});
|
|
}
|
|
};
|
|
|
|
const draw = (t) => {
|
|
ctx.clearRect(0, 0, w, h);
|
|
for (const s of stars) {
|
|
const a = s.baseAlpha * (0.5 + 0.5 * Math.sin(t * s.twinkleSpeed + s.twinklePhase));
|
|
ctx.beginPath();
|
|
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
|
ctx.fillStyle = s.hue === 'moon'
|
|
? `rgba(200, 230, 255, ${a})`
|
|
: `rgba(244, 236, 207, ${a})`;
|
|
ctx.fill();
|
|
}
|
|
for (const d of drift) {
|
|
d.x += d.vx;
|
|
d.y += d.vy;
|
|
if (d.x < 0) d.x = w; if (d.x > w) d.x = 0;
|
|
if (d.y < 0) d.y = h; if (d.y > h) d.y = 0;
|
|
const grad = ctx.createRadialGradient(d.x, d.y, 0, d.x, d.y, d.r * 6);
|
|
grad.addColorStop(0, `rgba(176, 136, 238, ${d.alpha})`);
|
|
grad.addColorStop(1, 'rgba(176, 136, 238, 0)');
|
|
ctx.fillStyle = grad;
|
|
ctx.beginPath();
|
|
ctx.arc(d.x, d.y, d.r * 6, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
raf = requestAnimationFrame(draw);
|
|
};
|
|
|
|
resize();
|
|
draw(0);
|
|
window.addEventListener('resize', resize);
|
|
return () => {
|
|
cancelAnimationFrame(raf);
|
|
window.removeEventListener('resize', resize);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<div className="bg">
|
|
<div className="bg-moon" aria-hidden="true"></div>
|
|
<div className="bg-fog" aria-hidden="true"></div>
|
|
</div>
|
|
<canvas id="particles" ref={canvasRef} aria-hidden="true" />
|
|
</>
|
|
);
|
|
}
|