牛顿大炮 html 完整代码
时间: 2025-09-30 10:55:33
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>牛顿大炮</title>
<style>
body {
font-family: Arial, sans-serif;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
}
canvas {
border: 1px solid #ccc;
background: #f8f8f8;
}
.controls {
margin-top: 15px;
display: flex;
gap: 15px;
}
.slider-group {
margin-top: 15px;
padding: 10px;
background: #fff;
border-radius: 8px;
width: 400px;
}
.slider-group label {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.slider-group span {
width: 50px;
display: inline-block;
}
.slider-group input[type="range"] {
flex: 1;
margin-left: 10px;
}
button {
width: 80px;
height: 40px;
border: none;
color: #fff;
font-weight: bold;
cursor: pointer;
border-radius: 6px;
}
#toggleBtn { background: #4CAF50; }
#resetBtn { background: #2196F3; }
#gSlider {
background: linear-gradient(to right, green, red);
height: 6px;
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
/* 自定义滑块小圆点 */
#gSlider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #fff;
border: 2px solid #666;
cursor: pointer;
}
</style>
</head>
<body>
<h2>牛顿大炮</h2>
<canvas id="cannonCanvas" width="420" height="380"></canvas>
<div>
<button id="toggleBtn">发射</button>
<button id="resetBtn">重置</button>
</div>
<div>
<strong>初速度设置</strong>
<label>
<span>Vx:</span>
<span id="vxVal">20</span>
<input id="vxSlider" type="range" min="-60" max="60" step="2" value="20">
</label>
<label>
<span>Vy:</span>
<span id="vyVal">-80</span>
<input id="vySlider" type="range" min="-120" max="60" step="2" value="-80">
</label>
<label>
<span>G:</span>
<span id="gVal">200</span>
<input id="gSlider" type="range" min="100" max="300" step="5" value="200">
</label>
</div>
<script>
const canvas = document.getElementById('cannonCanvas');
const ctx = canvas.getContext('2d');
// 画布参数
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
// 地球参数
const earthX = canvasWidth / 2;
const earthY = canvasHeight - 120;
const earthRadius = 60;
let G = 200; // 引力常数
const M = 2000; // 地球质量(虚拟值)
// 炮弹
let projectile = {
x: earthX + earthRadius + 10,
y: earthY,
vx: 20,
vy: -80,
radius: 6,
color: '#FF5722',
launched: false
};
// 轨迹
let trail = [];
const maxTrailLength = 200;
let isPlaying = false;
let lastTime = 0;
let timerId = null;
// 绘制场景
function drawScene() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// 地球
ctx.beginPath();
ctx.arc(earthX, earthY, earthRadius, 0, 2 * Math.PI);
ctx.fillStyle = '#2196F3';
ctx.fill();
// 轨迹
if (trail.length > 1) {
ctx.beginPath();
ctx.moveTo(trail[0].x, trail[0].y);
for (let i = 1; i < trail.length; i++) {
ctx.lineTo(trail[i].x, trail[i].y);
}
ctx.strokeStyle = '#9E9E9E';
ctx.lineWidth = 1;
ctx.stroke();
}
// 炮弹
ctx.beginPath();
ctx.arc(projectile.x, projectile.y, projectile.radius, 0, 2 * Math.PI);
ctx.fillStyle = projectile.color;
ctx.fill();
// 速度矢量
const scale = 0.5;
ctx.beginPath();
ctx.setLineDash([6, 4]);
ctx.moveTo(projectile.x, projectile.y);
ctx.lineTo(projectile.x + projectile.vx * scale, projectile.y + projectile.vy * scale);
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.setLineDash([]);
}
function updateProjectile(dt) {
let dx = projectile.x - earthX;
let dy = projectile.y - earthY;
let r2 = dx * dx + dy * dy;
let r = Math.sqrt(r2);
if (r <= earthRadius) {
stopAnimation();
return;
}
let a = G * M / r2;
let ax = -a * dx / r;
let ay = -a * dy / r;
projectile.vx += ax * dt;
projectile.vy += ay * dt;
projectile.x += projectile.vx * dt;
projectile.y += projectile.vy * dt;
trail.push({ x: projectile.x, y: projectile.y });
if (trail.length > maxTrailLength) {
trail.shift();
}
}
function startAnimation() {
projectile.launched = true;
lastTime = Date.now();
trail = [];
if (timerId) clearInterval(timerId);
timerId = setInterval(() => {
if (isPlaying) {
let now = Date.now();
let dt = (now - lastTime) / 1000;
lastTime = now;
updateProjectile(dt);
drawScene();
}
}, 16);
}
function stopAnimation() {
isPlaying = false;
clearInterval(timerId);
timerId = null;
document.getElementById('toggleBtn').textContent = '发射';
document.getElementById('toggleBtn').style.background = '#4CAF50';
}
function resetAnimation() {
stopAnimation();
trail = [];
projectile = {
x: earthX + earthRadius + 10,
y: earthY,
vx: 20,
vy: -80,
radius: 6,
color: '#FF5722',
launched: false
};
drawScene();
}
// 控制按钮
document.getElementById('toggleBtn').onclick = () => {
isPlaying = !isPlaying;
if (isPlaying) {
startAnimation();
document.getElementById('toggleBtn').textContent = '暂停';
document.getElementById('toggleBtn').style.background = '#FF6B6B';
} else {
stopAnimation();
}
};
document.getElementById('resetBtn').onclick = resetAnimation;
// 滑块
const vxSlider = document.getElementById('vxSlider');
const vySlider = document.getElementById('vySlider');
const gSlider = document.getElementById('gSlider');
vxSlider.oninput = e => {
projectile.vx = parseFloat(e.target.value);
document.getElementById('vxVal').textContent = projectile.vx.toFixed(1);
drawScene();
};
vySlider.oninput = e => {
projectile.vy = parseFloat(e.target.value);
document.getElementById('vyVal').textContent = projectile.vy.toFixed(1);
drawScene();
};
gSlider.oninput = e => {
G = parseFloat(e.target.value);
document.getElementById('gVal').textContent = G.toFixed(0);
drawScene();
};
drawScene();
</script>
</body>
</html>
