【前端】弹球特效(重力模拟)
import React, { useRef, useEffect } from 'react';
const BouncingBallSimulation = () => {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let animationFrameId;
// Set canvas size
canvas.width = 900;
canvas.height = 1600;
// Circle properties
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) * 0.7;
// Ball properties
const ballRadius = 15;
let x = centerX;
let y = centerY - radius + ballRadius;
let dx = 2;
let dy = 0;
// Gravity and friction
const gravity = 0.2;
const friction = 0.99;
// Matrix rain properties
const fontSize = 14;
const columns = canvas.width / fontSize;
const drops = [];
for (let i = 0; i < columns; i++) {
drops[i] = 1;
}
const drawMatrixRain = () => {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#0F0';
ctx.font = `${fontSize}px monospace`;
for (let i = 0; i < drops.length; i++) {
const text = String.fromCharCode(0x30A0 + Math.random() * 96);
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i]++;
}
};
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw matrix rain
drawMatrixRain();
// Draw circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.stroke();
// Draw ball
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
// Apply gravity
dy += gravity;
// Ball movement and collision detection
const nextX = x + dx;
const nextY = y + dy;
const distanceFromCenter = Math.sqrt((nextX - centerX) ** 2 + (nextY - centerY) ** 2);
if (distanceFromCenter + ballRadius > radius) {
// Calculate the normal vector
const nx = (nextX - centerX) / distanceFromCenter;
const ny = (nextY - centerY) / distanceFromCenter;
// Calculate the dot product
const dotProduct = dx * nx + dy * ny;
// Update velocity
dx = (dx - 2 * dotProduct * nx) * friction;
dy = (dy - 2 * dotProduct * ny) * friction;
} else {
x = nextX;
y = nextY;
}
animationFrameId = requestAnimationFrame(draw);
};
draw();
return () => {
cancelAnimationFrame(animationFrameId);
};
}, []);
return (
<div className="flex items-center justify-center w-full h-full bg-black">
<canvas ref={canvasRef} className="border border-gray-300" />
</div>
);
};
export default BouncingBallSimulation;