lovart3/components/antigravity-background.tsx
2025-12-17 20:15:18 +08:00

255 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useRef, useState } from "react";
import { useTheme } from "next-themes";
import Matter from "matter-js";
/**
* Antigravity Background Component
* 使用 Matter.js 实现完整的 Google Antigravity 物理效果
*/
export function AntigravityBackground() {
const sceneRef = useRef<HTMLDivElement>(null);
const engineRef = useRef<Matter.Engine | null>(null);
const renderRef = useRef<Matter.Render | null>(null);
const mouseConstraintRef = useRef<Matter.MouseConstraint | null>(null);
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!mounted || !sceneRef.current) return;
// 清理旧实例
if (engineRef.current) {
Matter.Engine.clear(engineRef.current);
}
if (renderRef.current) {
Matter.Render.stop(renderRef.current);
renderRef.current.canvas.remove();
}
// 创建物理引擎
const engine = Matter.Engine.create({
gravity: { x: 0, y: 1, scale: 0.001 },
});
engineRef.current = engine;
// 创建渲染器
const render = Matter.Render.create({
element: sceneRef.current,
engine: engine,
options: {
width: window.innerWidth,
height: window.innerHeight,
wireframes: false,
background: "transparent",
pixelRatio: window.devicePixelRatio,
},
});
renderRef.current = render;
// 创建墙壁(边界)
const wallOptions: Matter.IBodyDefinition = {
isStatic: true,
render: {
fillStyle: "transparent",
strokeStyle: "transparent",
},
friction: 0.3,
restitution: 0.6,
};
const walls = [
// 底部
Matter.Bodies.rectangle(
window.innerWidth / 2,
window.innerHeight + 25,
window.innerWidth,
50,
wallOptions
),
// 顶部
Matter.Bodies.rectangle(
window.innerWidth / 2,
-25,
window.innerWidth,
50,
wallOptions
),
// 左侧
Matter.Bodies.rectangle(
-25,
window.innerHeight / 2,
50,
window.innerHeight,
wallOptions
),
// 右侧
Matter.Bodies.rectangle(
window.innerWidth + 25,
window.innerHeight / 2,
50,
window.innerHeight,
wallOptions
),
];
Matter.Composite.add(engine.world, walls);
// 创建可拖动的物体
const emojis = [
{ emoji: "🤖", x: 200, y: 100, size: 40, color: "#667eea" },
{ emoji: "💬", x: 400, y: 150, size: 35, color: "#764ba2" },
{ emoji: "✨", x: 600, y: 200, size: 38, color: "#f093fb" },
{ emoji: "🚀", x: 800, y: 120, size: 36, color: "#4facfe" },
{ emoji: "🎨", x: 300, y: 400, size: 34, color: "#43e97b" },
{ emoji: "⚡", x: 700, y: 350, size: 37, color: "#fa709a" },
];
const bodies = emojis.map((item) => {
const body = Matter.Bodies.circle(item.x, item.y, item.size / 2, {
restitution: 0.8,
friction: 0.05,
density: 0.001,
render: {
fillStyle: item.color + "40",
strokeStyle: item.color + "80",
lineWidth: 2,
},
});
// 存储 emoji 信息到 body 上
(body as any).emoji = item.emoji;
(body as any).size = item.size;
return body;
});
Matter.Composite.add(engine.world, bodies);
// 添加鼠标控制
const mouse = Matter.Mouse.create(render.canvas);
const mouseConstraint = Matter.MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: {
visible: false,
},
},
});
mouseConstraintRef.current = mouseConstraint;
Matter.Composite.add(engine.world, mouseConstraint);
// 保持鼠标与渲染器同步
render.mouse = mouse;
// 自定义渲染函数(绘制 emoji)
Matter.Events.on(render, "afterRender", () => {
const context = render.context;
bodies.forEach((body) => {
const emoji = (body as any).emoji;
const size = (body as any).size;
if (!emoji) return;
context.save();
context.translate(body.position.x, body.position.y);
context.rotate(body.angle);
// 绘制半透明背景圆
context.beginPath();
context.arc(0, 0, size / 2, 0, Math.PI * 2);
context.fillStyle = body.render.fillStyle || "#667eea40";
context.fill();
// 绘制 emoji
context.font = `${size * 0.8}px Arial`;
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText(emoji, 0, 0);
context.restore();
});
});
// 启动引擎和渲染器
const runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);
Matter.Render.run(render);
// 窗口大小调整
const handleResize = () => {
render.canvas.width = window.innerWidth;
render.canvas.height = window.innerHeight;
render.options.width = window.innerWidth;
render.options.height = window.innerHeight;
// 更新墙壁位置
Matter.Body.setPosition(walls[0], {
x: window.innerWidth / 2,
y: window.innerHeight + 25,
});
Matter.Body.setPosition(walls[1], {
x: window.innerWidth / 2,
y: -25,
});
Matter.Body.setPosition(walls[2], {
x: -25,
y: window.innerHeight / 2,
});
Matter.Body.setPosition(walls[3], {
x: window.innerWidth + 25,
y: window.innerHeight / 2,
});
// 更新墙壁尺寸
Matter.Body.scale(walls[0], window.innerWidth / walls[0].bounds.max.x, 1);
Matter.Body.scale(walls[1], window.innerWidth / walls[1].bounds.max.x, 1);
Matter.Body.scale(walls[2], 1, window.innerHeight / walls[2].bounds.max.y);
Matter.Body.scale(walls[3], 1, window.innerHeight / walls[3].bounds.max.y);
};
window.addEventListener("resize", handleResize);
// 清理函数
return () => {
window.removeEventListener("resize", handleResize);
if (mouseConstraintRef.current) {
Matter.Composite.remove(engine.world, mouseConstraintRef.current);
}
if (renderRef.current) {
Matter.Render.stop(renderRef.current);
renderRef.current.canvas.remove();
}
if (engineRef.current) {
Matter.Engine.clear(engineRef.current);
}
Matter.Runner.stop(runner);
};
}, [mounted, resolvedTheme]);
if (!mounted) return null;
return (
<div
ref={sceneRef}
className="fixed inset-0 z-0"
style={{
filter: resolvedTheme === "dark" ? "brightness(0.8)" : "brightness(1)",
transition: "filter 0.5s ease-in-out",
}}
/>
);
}