前言
今天在豆包的AI应用上看到了一个抛物线的功能,其实不仅仅是抛物线,其它的效果也是可以的,我根据基础的示例做了个效果,效果再下图中能看到,使用的是纯HTML+Canvas来完成的,效果还不错,我们用来学习数学和物理都还不错,那么接下来我们来具体看看吧。
目录
在线实操效果
运行可以直接看到效果。
抛物线模型特点
直观的用户界面:采用现代卡片式设计,清晰分离控制面板和模拟区域,提供舒适的视觉体验。
完整的物理模拟:
支持调整初速度 (5-50 m/s) 和发射角度 (10-80 度)
实时计算并显示最大高度、飞行时间和水平距离
模拟真实的重力加速度效果
丰富的视觉元素:
清晰的坐标轴和网格线
平滑的轨迹绘制
物体落地时的爆炸动画效果
渐变色彩和阴影效果增强深度感
轨迹方程展示:
显示标准参数方程
实时计算并更新当前轨迹方程
对照表
从初速度10m/s,角度10°开始递增变化,下方是整个对照表。
初速度 (m/s) | 角度 (°) | 最大高度 (m) | 飞行时间 (s) | 水平距离 (m) | 轨迹特点 |
---|---|---|---|---|---|
10 | 10 | 0.15 | 0.35 | 3.42 | 低而短的轨迹,几乎是水平飞行 |
10 | 15 | 0.34 | 0.53 | 5.00 | 略微上扬的短轨迹 |
10 | 20 | 0.60 | 0.70 | 6.43 | 中等高度的短轨迹 |
10 | 25 | 0.92 | 0.86 | 7.66 | 明显的抛物线形状,中等距离 |
10 | 30 | 1.28 | 1.02 | 8.84 | 典型的抛物线轨迹,距离适中 |
10 | 35 | 1.68 | 1.18 | 9.92 | 高度增加,距离接近最大值 |
10 | 40 | 2.11 | 1.32 | 10.88 | 接近 45 度角的最大距离 |
10 | 45 | 2.55 | 1.45 | 11.70 | 最大水平距离(理论上 45 度角时最大) |
10 | 50 | 2.99 | 1.57 | 12.35 | 高度继续增加,距离略有下降 |
10 | 55 | 3.41 | 1.68 | 12.81 | 高度增加明显,距离开始减少 |
10 | 60 | 3.83 | 1.77 | 13.06 | 高度高但距离开始减少 |
10 | 65 | 4.21 | 1.84 | 13.07 | 更高的轨迹,水平距离几乎不变 |
10 | 70 | 4.55 | 1.89 | 12.82 | 垂直方向分量更大,水平距离减少 |
10 | 75 | 4.83 | 1.92 | 12.29 | 几乎是垂直向上,水平距离较小 |
10 | 80 | 5.03 | 1.93 | 11.46 | 接近垂直向上,水平距离最小 |
20 | 10 | 0.61 | 0.70 | 13.89 | 低而远的轨迹,水平分量大 |
20 | 15 | 1.38 | 1.07 | 20.00 | 略微上扬的远距离轨迹 |
20 | 20 | 2.42 | 1.41 | 25.71 | 中等高度的远距离轨迹 |
20 | 25 | 3.71 | 1.73 | 30.65 | 明显的抛物线形状,远距离 |
20 | 30 | 5.14 | 2.05 | 35.36 | 典型的抛物线轨迹,远距离 |
20 | 35 | 6.73 | 2.37 | 39.68 | 高度增加,距离接近最大值 |
20 | 40 | 8.46 | 2.65 | 43.52 | 接近 45 度角的最大距离 |
20 | 45 | 10.20 | 2.90 | 46.80 | 最大水平距离(理论上 45 度角时最大) |
20 | 50 | 11.95 | 3.15 | 49.41 | 高度继续增加,距离略有下降 |
20 | 55 | 13.65 | 3.36 | 51.23 | 高度增加明显,距离开始减少 |
20 | 60 | 15.30 | 3.55 | 52.25 | 高度高但距离开始减少 |
20 | 65 | 16.85 | 3.69 | 52.29 | 更高的轨迹,水平距离几乎不变 |
20 | 70 | 18.20 | 3.79 | 51.27 | 垂直方向分量更大,水平距离减少 |
20 | 75 | 19.33 | 3.85 | 49.17 | 几乎是垂直向上,水平距离较小 |
20 | 80 | 20.13 | 3.86 | 45.83 | 接近垂直向上,水平距离最小 |
30 | 10 | 1.37 | 1.05 | 31.25 | 低而远的轨迹,水平分量大 |
30 | 15 | 3.10 | 1.60 | 45.00 | 略微上扬的远距离轨迹 |
30 | 20 | 5.45 | 2.12 | 57.89 | 中等高度的远距离轨迹 |
30 | 25 | 8.35 | 2.60 | 69.09 | 明显的抛物线形状,远距离 |
30 | 30 | 11.57 | 3.07 | 79.56 | 典型的抛物线轨迹,远距离 |
30 | 35 | 15.14 | 3.55 | 89.27 | 高度增加,距离接近最大值 |
30 | 40 | 19.04 | 3.97 | 98.17 | 接近 45 度角的最大距离 |
30 | 45 | 23.05 | 4.35 | 105.31 | 最大水平距离(理论上 45 度角时最大) |
30 | 50 | 27.10 | 4.72 | 111.18 | 高度继续增加,距离略有下降 |
30 | 55 | 31.16 | 5.04 | 115.26 | 高度增加明显,距离开始减少 |
30 | 60 | 35.17 | 5.32 | 117.56 | 高度高但距离开始减少 |
30 | 65 | 39.02 | 5.54 | 117.65 | 更高的轨迹,水平距离几乎不变 |
30 | 70 | 42.68 | 5.69 | 115.35 | 垂直方向分量更大,水平距离减少 |
30 | 75 | 46.04 | 5.78 | 110.64 | 几乎是垂直向上,水平距离较小 |
30 | 80 | 48.99 | 5.80 | 103.12 | 接近垂直向上,水平距离最小 |
40 | 10 | 2.43 | 1.40 | 55.56 | 低而远的轨迹,水平分量大 |
40 | 15 | 5.50 | 2.13 | 80.00 | 略微上扬的远距离轨迹 |
40 | 20 | 9.73 | 2.83 | 102.86 | 中等高度的远距离轨迹 |
40 | 25 | 15.10 | 3.47 | 124.44 | 明显的抛物线形状,远距离 |
40 | 30 | 21.54 | 4.10 | 144.65 | 典型的抛物线轨迹,远距离 |
40 | 35 | 28.99 | 4.73 | 163.30 | 高度增加,距离接近最大值 |
40 | 40 | 37.40 | 5.30 | 180.34 | 接近 45 度角的最大距离 |
40 | 45 | 46.72 | 5.80 | 195.65 | 最大水平距离(理论上 45 度角时最大) |
40 | 50 | 56.89 | 6.30 | 209.12 | 高度继续增加,距离略有下降 |
40 | 55 | 67.89 | 6.72 | 220.50 | 高度增加明显,距离开始减少 |
40 | 60 | 79.68 | 7.10 | 229.62 | 高度高但距离开始减少 |
40 | 65 | 92.07 | 7.39 | 236.33 | 更高的轨迹,水平距离几乎不变 |
40 | 70 | 104.98 | 7.58 | 240.47 | 垂直方向分量更大,水平距离减少 |
40 | 75 | 118.30 | 7.70 | 241.83 | 几乎是垂直向上,水平距离较小 |
40 | 80 | 131.97 | 7.73 | 239.85 | 接近垂直向上,水平距离最小 |
50 | 10 | 3.80 | 1.75 | 86.81 | 低而远的轨迹,水平分量大 |
50 | 15 | 8.59 | 2.67 | 125.00 | 略微上扬的远距离轨迹 |
50 | 20 | 15.20 | 3.54 | 160.71 | 中等高度的远距离轨迹 |
50 | 25 | 23.60 | 4.34 | 194.01 | 明显的抛物线形状,远距离 |
50 | 30 | 33.81 | 5.13 | 225.79 | 典型的抛物线轨迹,远距离 |
50 | 35 | 45.81 | 5.91 | 255.92 | 高度增加,距离接近最大值 |
50 | 40 | 59.60 | 6.62 | 284.34 | 接近 45 度角的最大距离 |
50 | 45 | 75.16 | 7.25 | 310.93 | 最大水平距离(理论上 45 度角时最大) |
50 | 50 | 92.34 | 7.90 | 335.59 | 高度继续增加,距离略有下降 |
50 | 55 | 111.18 | 8.40 | 358.28 | 高度增加明显,距离开始减少 |
50 | 60 | 131.57 | 8.88 | 378.86 | 高度高但距离开始减少 |
50 | 65 | 153.39 | 9.24 | 397.24 | 更高的轨迹,水平距离几乎不变 |
50 | 70 | 176.53 | 9.48 | 413.23 | 垂直方向分量更大,水平距离减少 |
50 | 75 | 200.87 | 9.63 | 426.66 | 几乎是垂直向上,水平距离较小 |
50 | 80 | 226.36 | 9.67 | 437.37 | 接近垂直向上,水平距离最小 |
完整代码块
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>抛物体运动模拟器</title>
<!-- 引入Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- Tailwind配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3B82F6',
secondary: '#10B981',
accent: '#F59E0B',
dark: '#1E293B',
light: '#F8FAFC'
},
fontFamily: {
inter: ['Inter', 'sans-serif'],
},
},
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.simulator-shadow {
box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 8px 10px -6px rgba(59, 130, 246, 0.1);
}
.slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #3B82F6;
cursor: pointer;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
.slider-thumb:hover {
box-shadow: 0 0 0 5px rgba(59, 130, 246, 0.3);
}
.equation-card {
backdrop-filter: blur(8px);
background-color: rgba(255, 255, 255, 0.85);
}
}
</style>
<!-- 引入Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body class="bg-gradient-to-br from-light to-gray-100 min-h-screen font-inter text-dark">
<!-- 页面头部 -->
<header class="bg-white shadow-md py-4 px-6 fixed w-full z-10 transition-all duration-300" id="header">
<div class="container mx-auto flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa fa-rocket text-primary text-2xl"></i>
<h1 class="text-xl md:text-2xl font-bold text-dark">抛物体运动模拟器</h1>
</div>
<div class="flex items-center space-x-4">
<button id="info-btn" class="p-2 rounded-full hover:bg-gray-100 transition-colors">
<i class="fa fa-info-circle text-primary"></i>
</button>
<button id="reset-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg shadow-md hover:shadow-lg transition-all">
<i class="fa fa-refresh mr-1"></i> 重置
</button>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="container mx-auto pt-24 pb-16 px-4 md:px-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- 控制面板 -->
<div class="lg:col-span-1 order-2 lg:order-1">
<div class="bg-white rounded-xl p-6 simulator-shadow h-full">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fa fa-sliders text-primary mr-2"></i> 控制面板
</h2>
<div class="space-y-6">
<!-- 初速度控制 -->
<div class="space-y-2">
<div class="flex justify-between items-center">
<label for="velocity" class="text-sm font-medium">初速度 (m/s)</label>
<span id="velocity-value" class="text-sm font-semibold text-primary">20</span>
</div>
<input
type="range"
id="velocity"
min="5"
max="50"
step="1"
value="20"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
>
</div>
<!-- 发射角度控制 -->
<div class="space-y-2">
<div class="flex justify-between items-center">
<label for="angle" class="text-sm font-medium">发射角度 (°)</label>
<span id="angle-value" class="text-sm font-semibold text-primary">45</span>
</div>
<input
type="range"
id="angle"
min="10"
max="80"
step="1"
value="45"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
>
</div>
<!-- 物理参数 -->
<div class="space-y-2">
<h3 class="text-sm font-medium">物理参数</h3>
<div class="grid grid-cols-2 gap-3">
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-xs text-gray-500">重力加速度</p>
<p class="text-sm font-semibold" id="gravity-value">9.8 m/s²</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-xs text-gray-500">最大高度</p>
<p class="text-sm font-semibold" id="max-height">--</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-xs text-gray-500">飞行时间</p>
<p class="text-sm font-semibold" id="flight-time">--</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-xs text-gray-500">水平距离</p>
<p class="text-sm font-semibold" id="horizontal-distance">--</p>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex space-x-3 pt-2">
<button id="start-btn" class="flex-1 bg-secondary hover:bg-secondary/90 text-white py-2 rounded-lg shadow-md hover:shadow-lg transition-all">
<i class="fa fa-play mr-1"></i> 开始
</button>
<button id="pause-btn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-dark py-2 rounded-lg shadow-md hover:shadow-lg transition-all" disabled>
<i class="fa fa-pause mr-1"></i> 暂停
</button>
</div>
</div>
</div>
</div>
<!-- 模拟器和轨迹方程 -->
<div class="lg:col-span-2 order-1 lg:order-2">
<div class="relative">
<!-- 模拟器画布 -->
<div class="bg-white rounded-xl overflow-hidden simulator-shadow mb-6 relative">
<canvas id="simulator" class="w-full aspect-[16/9]"></canvas>
<!-- 坐标轴和网格线 -->
<div class="absolute inset-0 pointer-events-none">
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-gray-300"></div>
<div class="absolute bottom-0 left-0 w-0.5 h-full bg-gray-300"></div>
<!-- 刻度标记 -->
<div class="absolute bottom-0 left-0 w-full flex justify-between px-4 py-2 text-xs text-gray-500">
<span>0</span>
<span>10m</span>
<span>20m</span>
<span>30m</span>
<span>40m</span>
<span>50m</span>
</div>
<div class="absolute bottom-0 left-0 h-full flex flex-col justify-between px-2 py-4 text-xs text-gray-500">
<span class="rotate-90">0</span>
<span class="rotate-90">5m</span>
<span class="rotate-90">10m</span>
<span class="rotate-90">15m</span>
<span class="rotate-90">20m</span>
</div>
</div>
</div>
<!-- 轨迹方程卡片 -->
<div class="equation-card p-4 rounded-xl border border-gray-200 shadow-lg">
<h2 class="text-lg font-semibold mb-2 flex items-center">
<i class="fa fa-line-chart text-primary mr-2"></i> 轨迹方程
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-xs text-gray-500">参数方程</p>
<p class="text-sm font-medium" id="parametric-equation">x = v₀·cos(θ)·t<br>y = v₀·sin(θ)·t - ½·g·t²</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-xs text-gray-500">标准方程</p>
<p class="text-sm font-medium" id="standard-equation">y = tan(θ)·x - (g·x²)/(2·v₀²·cos²(θ))</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg md:col-span-2">
<p class="text-xs text-gray-500">当前轨迹方程</p>
<p class="text-sm font-semibold" id="current-equation">y = 0.0000x² + 0.0000x + 0.0000</p>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- 信息模态框 -->
<div id="info-modal" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-xl p-6 max-w-md w-full mx-4 transform transition-all duration-300 scale-95 opacity-0" id="modal-content">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">关于抛物体运动</h2>
<button id="close-modal" class="text-gray-500 hover:text-gray-700">
<i class="fa fa-times text-xl"></i>
</button>
</div>
<div class="space-y-4 text-sm">
<p>抛物体运动是物理学中研究物体在重力作用下的运动轨迹的现象。当物体以一定初速度和角度抛出时,它会沿抛物线轨迹运动。</p>
<p>本模拟器展示了抛物体运动的基本原理,您可以通过调整初速度和发射角度来观察不同条件下的运动轨迹。</p>
<div class="bg-gray-50 p-3 rounded-lg">
<h3 class="font-semibold mb-2">物理公式:</h3>
<ul class="list-disc pl-5 space-y-1">
<li>水平位移: x = v₀·cos(θ)·t</li>
<li>垂直位移: y = v₀·sin(θ)·t - ½·g·t²</li>
<li>最大高度: h = (v₀·sin(θ))²/(2·g)</li>
<li>飞行时间: T = 2·v₀·sin(θ)/g</li>
<li>水平距离: R = v₀²·sin(2·θ)/g</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="bg-white py-4 px-6 shadow-inner">
<div class="container mx-auto text-center text-sm text-gray-500">
<p>抛物体运动模拟器 © 2025 | 使用 HTML, Canvas, Tailwind CSS 构建·teacher_fan引用于豆包AI</p>
</div>
</footer>
<!-- JavaScript -->
<script>
// 获取DOM元素
const simulator = document.getElementById('simulator');
const ctx = simulator.getContext('2d');
const velocitySlider = document.getElementById('velocity');
const angleSlider = document.getElementById('angle');
const velocityValue = document.getElementById('velocity-value');
const angleValue = document.getElementById('angle-value');
const maxHeightValue = document.getElementById('max-height');
const flightTimeValue = document.getElementById('flight-time');
const horizontalDistanceValue = document.getElementById('horizontal-distance');
const startBtn = document.getElementById('start-btn');
const pauseBtn = document.getElementById('pause-btn');
const resetBtn = document.getElementById('reset-btn');
const infoBtn = document.getElementById('info-btn');
const infoModal = document.getElementById('info-modal');
const modalContent = document.getElementById('modal-content');
const closeModal = document.getElementById('close-modal');
const currentEquation = document.getElementById('current-equation');
const header = document.getElementById('header');
// 设置Canvas尺寸
function resizeCanvas() {
const container = simulator.parentElement;
simulator.width = container.clientWidth;
simulator.height = container.clientWidth * 9 / 16;
}
// 监听窗口大小变化
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// 物理参数
const GRAVITY = 9.8; // 重力加速度 (m/s²)
let velocity = 20; // 初速度 (m/s)
let angle = 45; // 发射角度 (度)
let time = 0; // 时间 (s)
let dt = 0.05; // 时间步长
let animationId = null;
let isRunning = false;
let trajectoryPoints = [];
let explosionParticles = [];
// 坐标转换函数 (物理坐标 -> 画布坐标)
function physicsToCanvas(x, y) {
const scaleX = simulator.width / 60; // 60m 映射到画布宽度
const scaleY = simulator.height / 25; // 25m 映射到画布高度
return {
x: x * scaleX + 20, // 偏移20像素以适应坐标轴
y: simulator.height - (y * scaleY + 20) // y轴翻转并偏移
};
}
// 坐标转换函数 (画布坐标 -> 物理坐标)
function canvasToPhysics(x, y) {
const scaleX = simulator.width / 60;
const scaleY = simulator.height / 25;
return {
x: (x - 20) / scaleX,
y: (simulator.height - y - 20) / scaleY
};
}
// 计算抛物体位置
function calculatePosition(time) {
const rad = angle * Math.PI / 180;
const x = velocity * Math.cos(rad) * time;
const y = velocity * Math.sin(rad) * time - 0.5 * GRAVITY * time * time;
return { x, y };
}
// 计算最大高度
function calculateMaxHeight() {
const rad = angle * Math.PI / 180;
return (velocity * Math.sin(rad)) ** 2 / (2 * GRAVITY);
}
// 计算飞行时间
function calculateFlightTime() {
const rad = angle * Math.PI / 180;
return 2 * velocity * Math.sin(rad) / GRAVITY;
}
// 计算水平距离
function calculateHorizontalDistance() {
const rad = angle * Math.PI / 180;
return velocity ** 2 * Math.sin(2 * rad) / GRAVITY;
}
// 更新物理参数显示
function updatePhysicsValues() {
maxHeightValue.textContent = calculateMaxHeight().toFixed(2) + " m";
flightTimeValue.textContent = calculateFlightTime().toFixed(2) + " s";
horizontalDistanceValue.textContent = calculateHorizontalDistance().toFixed(2) + " m";
// 更新当前轨迹方程
updateTrajectoryEquation();
}
// 更新轨迹方程
function updateTrajectoryEquation() {
const rad = angle * Math.PI / 180;
const a = -GRAVITY / (2 * velocity ** 2 * Math.cos(rad) ** 2);
const b = Math.tan(rad);
const c = 0;
currentEquation.textContent = `y = ${a.toFixed(4)}x² + ${b.toFixed(4)}x + ${c.toFixed(4)}`;
}
// 绘制背景和坐标轴
function drawBackground() {
ctx.fillStyle = '#F8FAFC';
ctx.fillRect(0, 0, simulator.width, simulator.height);
// 绘制坐标轴
ctx.strokeStyle = '#94A3B8';
ctx.lineWidth = 1;
// x轴
ctx.beginPath();
ctx.moveTo(20, simulator.height - 20);
ctx.lineTo(simulator.width, simulator.height - 20);
ctx.stroke();
// y轴
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(20, simulator.height - 20);
ctx.stroke();
// 绘制网格
ctx.strokeStyle = '#E2E8F0';
ctx.lineWidth = 0.5;
// 垂直网格
for (let x = 0; x <= 60; x += 10) {
const canvasX = physicsToCanvas(x, 0).x;
ctx.beginPath();
ctx.moveTo(canvasX, 0);
ctx.lineTo(canvasX, simulator.height - 20);
ctx.stroke();
}
// 水平网格
for (let y = 0; y <= 25; y += 5) {
const canvasY = physicsToCanvas(0, y).y;
ctx.beginPath();
ctx.moveTo(20, canvasY);
ctx.lineTo(simulator.width, canvasY);
ctx.stroke();
}
}
// 绘制轨迹
function drawTrajectory() {
if (trajectoryPoints.length < 2) return;
ctx.strokeStyle = '#3B82F6';
ctx.lineWidth = 2;
ctx.beginPath();
// 绘制轨迹线
ctx.moveTo(trajectoryPoints[0].x, trajectoryPoints[0].y);
for (let i = 1; i < trajectoryPoints.length; i++) {
ctx.lineTo(trajectoryPoints[i].x, trajectoryPoints[i].y);
}
ctx.stroke();
// 绘制当前位置
const currentPos = trajectoryPoints[trajectoryPoints.length - 1];
ctx.fillStyle = '#3B82F6';
ctx.beginPath();
ctx.arc(currentPos.x, currentPos.y, 6, 0, Math.PI * 2);
ctx.fill();
}
// 绘制爆炸效果
function drawExplosion() {
if (explosionParticles.length === 0) return;
for (let i = 0; i < explosionParticles.length; i++) {
const p = explosionParticles[i];
// 更新粒子位置
p.x += p.vx;
p.y += p.vy;
p.vy += 0.1; // 重力影响
p.life -= 0.02;
// 绘制粒子
const gradient = ctx.createRadialGradient(
p.x, p.y, 0,
p.x, p.y, p.size
);
gradient.addColorStop(0, `rgba(${p.r}, ${p.g}, ${p.b}, ${p.life})`);
gradient.addColorStop(1, `rgba(${p.r}, ${p.g}, ${p.b}, 0)`);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
// 移除已消失的粒子
explosionParticles = explosionParticles.filter(p => p.life > 0);
}
// 创建爆炸效果
function createExplosion(x, y) {
explosionParticles = [];
for (let i = 0; i < 50; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 3;
const size = 2 + Math.random() * 4;
const life = 0.8 + Math.random() * 0.2;
explosionParticles.push({
x,
y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size,
life,
r: 255,
g: 100 + Math.random() * 155,
b: 0
});
}
}
// 主动画循环
function animate() {
drawBackground();
drawTrajectory();
drawExplosion();
if (isRunning) {
time += dt;
const pos = calculatePosition(time);
// 保存轨迹点
const canvasPos = physicsToCanvas(pos.x, pos.y);
trajectoryPoints.push(canvasPos);
// 检查是否落地
if (pos.y <= 0) {
isRunning = false;
startBtn.disabled = false;
pauseBtn.disabled = true;
createExplosion(canvasPos.x, canvasPos.y);
}
}
animationId = requestAnimationFrame(animate);
}
// 开始动画
function startAnimation() {
if (!isRunning) {
isRunning = true;
startBtn.disabled = true;
pauseBtn.disabled = false;
}
}
// 暂停动画
function pauseAnimation() {
if (isRunning) {
isRunning = false;
startBtn.disabled = false;
pauseBtn.disabled = true;
}
}
// 重置动画
function resetAnimation() {
pauseAnimation();
time = 0;
trajectoryPoints = [];
explosionParticles = [];
updatePhysicsValues();
}
// 显示信息模态框
function showInfoModal() {
infoModal.classList.remove('hidden');
setTimeout(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
}, 10);
}
// 隐藏信息模态框
function hideInfoModal() {
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
infoModal.classList.add('hidden');
}, 300);
}
// 滚动效果
function handleScroll() {
if (window.scrollY > 10) {
header.classList.add('py-2', 'shadow-lg');
header.classList.remove('py-4', 'shadow-md');
} else {
header.classList.add('py-4', 'shadow-md');
header.classList.remove('py-2', 'shadow-lg');
}
}
// 事件监听
velocitySlider.addEventListener('input', () => {
velocity = parseInt(velocitySlider.value);
velocityValue.textContent = velocity;
resetAnimation();
});
angleSlider.addEventListener('input', () => {
angle = parseInt(angleSlider.value);
angleValue.textContent = angle;
resetAnimation();
});
startBtn.addEventListener('click', startAnimation);
pauseBtn.addEventListener('click', pauseAnimation);
resetBtn.addEventListener('click', resetAnimation);
infoBtn.addEventListener('click', showInfoModal);
closeModal.addEventListener('click', hideInfoModal);
infoModal.addEventListener('click', (e) => {
if (e.target === infoModal) hideInfoModal();
});
window.addEventListener('scroll', handleScroll);
// 初始化
updatePhysicsValues();
drawBackground();
animate();
</script>
</body>
</html>