Files
OpenFlare/frontend/components/ui/snow-effect.tsx
T
2026-07-13 15:17:45 +08:00

97 lines
2.3 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
interface Snowflake {
x: number;
y: number;
radius: number;
speed: number;
opacity: number;
drift: number;
}
export function SnowEffect() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let animationFrameId: number;
let snowflakes: Snowflake[] = [];
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
const createSnowflakes = () => {
const count = Math.floor(window.innerWidth / 4);
snowflakes = [];
for (let i = 0; i < count; i++) {
snowflakes.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: Math.random() * 3 + 1,
speed: Math.random() * 2 + 0.5,
opacity: Math.random() * 0.5 + 0.3,
drift: Math.random() * 1 - 0.5,
});
}
};
const drawSnowflakes = () => {
if (!ctx || !canvas) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
snowflakes.forEach((flake) => {
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity})`;
ctx.fill();
flake.y += flake.speed;
flake.x += flake.drift;
if (flake.y > canvas.height) {
flake.y = -flake.radius;
flake.x = Math.random() * canvas.width;
}
if (flake.x > canvas.width) {
flake.x = 0;
} else if (flake.x < 0) {
flake.x = canvas.width;
}
});
animationFrameId = requestAnimationFrame(drawSnowflakes);
};
resizeCanvas();
createSnowflakes();
drawSnowflakes();
window.addEventListener('resize', () => {
resizeCanvas();
createSnowflakes();
});
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resizeCanvas);
};
}, []);
return (
<canvas
ref={canvasRef}
className='fixed inset-0 pointer-events-none z-50'
style={{ background: 'transparent' }}
/>
);
}