2024-12-12 23:27:36 +08:00
|
|
|
|
import { useEffect, useRef, memo } from "react";
|
|
|
|
|
import { useThemeMode } from "hooks/ThemeMode";
|
2024-12-08 19:19:54 +08:00
|
|
|
|
|
2024-12-09 02:25:32 +08:00
|
|
|
|
interface AnimatedBackgroundProps {
|
|
|
|
|
onError?: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
export const AnimatedBackground = memo(
|
|
|
|
|
({ onError }: AnimatedBackgroundProps) => {
|
|
|
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
|
const { mode } = useThemeMode();
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const canvas = canvasRef.current;
|
|
|
|
|
if (!canvas) {
|
2024-12-09 02:25:32 +08:00
|
|
|
|
onError?.();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-08 19:19:54 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
canvas.width = window.innerWidth;
|
|
|
|
|
canvas.height = window.innerHeight;
|
2024-12-09 02:25:32 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
try {
|
|
|
|
|
const ctx = canvas.getContext("2d", {
|
|
|
|
|
alpha: true,
|
|
|
|
|
desynchronized: true,
|
|
|
|
|
});
|
2024-12-08 19:19:54 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
if (!ctx) {
|
|
|
|
|
console.error("无法获取 canvas context");
|
|
|
|
|
onError?.();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-08 19:19:54 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
const context = ctx;
|
|
|
|
|
|
|
|
|
|
const getRandomHSLColor = () => {
|
|
|
|
|
const hue = Math.random() * 360;
|
|
|
|
|
const saturation = 90 + Math.random() * 10;
|
|
|
|
|
const lightness =
|
|
|
|
|
mode === "dark"
|
|
|
|
|
? 50 + Math.random() * 15 // 暗色模式:50-65%
|
|
|
|
|
: 60 + Math.random() * 15; // 亮色模式:60-75%
|
|
|
|
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ballColor = getRandomHSLColor();
|
|
|
|
|
let ballRadius = 100;
|
|
|
|
|
let x = canvas.width / 2;
|
|
|
|
|
let y = canvas.height - 200;
|
|
|
|
|
let dx = 0.2;
|
|
|
|
|
let dy = -0.2;
|
|
|
|
|
|
|
|
|
|
function drawBall() {
|
|
|
|
|
context.beginPath();
|
|
|
|
|
context.arc(x, y, ballRadius, 0, Math.PI * 2);
|
|
|
|
|
context.fillStyle = ballColor;
|
|
|
|
|
context.fill();
|
|
|
|
|
context.closePath();
|
|
|
|
|
}
|
2024-12-09 02:25:32 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
const fps = 30;
|
|
|
|
|
const interval = 1000 / fps;
|
|
|
|
|
let then = Date.now();
|
2024-12-09 02:25:32 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
const draw = () => {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
const delta = now - then;
|
2024-12-08 19:19:54 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
if (delta > interval) {
|
|
|
|
|
then = now - (delta % interval);
|
2024-12-08 19:19:54 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
|
drawBall();
|
2024-12-09 02:25:32 +08:00
|
|
|
|
|
2024-12-12 23:27:36 +08:00
|
|
|
|
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
|
|
|
|
|
dx = -dx;
|
|
|
|
|
}
|
|
|
|
|
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
|
|
|
|
|
dy = -dy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x += dx;
|
|
|
|
|
y += dy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
animationFrameId = requestAnimationFrame(draw);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let animationFrameId: number;
|
|
|
|
|
draw();
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (animationFrameId) {
|
|
|
|
|
cancelAnimationFrame(animationFrameId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Canvas 初始化失败:", error);
|
|
|
|
|
onError?.();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}, [mode, onError]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="fixed inset-0 -z-10 overflow-hidden">
|
|
|
|
|
<canvas
|
|
|
|
|
ref={canvasRef}
|
|
|
|
|
className="w-full h-full opacity-70"
|
|
|
|
|
style={{
|
|
|
|
|
filter: "blur(100px)",
|
|
|
|
|
position: "absolute",
|
|
|
|
|
top: 0,
|
|
|
|
|
left: 0,
|
|
|
|
|
willChange: "transform",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|