You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas实现抛射运动模拟 小球动画交互与运动逻辑修正

Canvas抛射运动模拟实现

修正内容

  • 补全按钮事件绑定,新增动画帧启停控制,避免多次点击提交导致多动画叠加、运动速率异常的问题
  • 替换原有斜向直线运动逻辑,严格按照斜抛运动物理规则计算坐标:
    • 水平方向不受力,做匀速直线运动
    • 竖直方向受重力作用,做匀变速运动,最终轨迹为标准抛物线
  • 适配Canvas坐标系(y轴向下为正),将抛射原点固定在画布左下角,添加地面参考线
  • 增加落地判定,小球接触地面后自动停止动画,避免飞出画布后持续运行
  • 增加缩放系数,调整速度、重力参数的数值映射,让运动速度符合视觉预期

完整实现代码

JavaScript部分

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 固定画布尺寸,避免坐标计算偏移
canvas.width = 800;
canvas.height = 500;

const velocitySlider = document.getElementById('velocityRange');
const angleSlider = document.getElementById('angleRange');
let velocityOutput = document.getElementById('velocityValue');
let angleOutput = document.getElementById('angleValue');
const submitBtn = document.getElementById('submitBtn');
const resetBtn = document.getElementById('resetBtn');

// 物理与显示参数
const g = 9.8; // 重力加速度
const scale = 3; // 物理单位到画布像素的缩放比
let animationId = null; // 存储动画帧ID,用于中断动画
let t = 0; // 运动累计时间
let oldTimeStamp = 0;
let initialV = 0; // 初始速度
let initialAngle = 0; // 初始角度(弧度值)
// 抛射原点坐标:画布左下角留边距,避免小球贴边显示
const originX = 20;
const originY = canvas.height - 20;
const ball = {
  x: originX,
  y: originY,
  radius: 10
};

// 初始化滑块显示默认值
angleOutput.innerHTML = angleSlider.value;
velocityOutput.innerHTML = velocitySlider.value;

// 滑块拖动实时更新显示值
angleSlider.oninput = function() {
  angleOutput.innerHTML = this.value;
};
velocitySlider.oninput = function() {
  velocityOutput.innerHTML = this.value;
};

// 重置功能
function handleReset(e) {
  e.preventDefault();
  // 停止正在运行的动画
  if(animationId) {
    cancelAnimationFrame(animationId);
    animationId = null;
  }
  // 重置滑块到默认值
  angleSlider.value = 45;
  angleOutput.innerHTML = angleSlider.value;
  velocitySlider.value = 25;
  velocityOutput.innerHTML = velocitySlider.value;
  // 重置小球位置、时间计数
  t = 0;
  oldTimeStamp = 0;
  ball.x = originX;
  ball.y = originY;
  // 重绘初始状态
  draw();
}

// 提交启动模拟
function handleSubmit(e) {
  e.preventDefault();
  // 先终止已有动画,避免多帧叠加
  if(animationId) cancelAnimationFrame(animationId);
  // 读取用户配置参数,角度转弧度供三角函数计算使用
  initialV = Number(velocitySlider.value);
  const angleDeg = Number(angleSlider.value);
  initialAngle = angleDeg * Math.PI / 180;
  // 重置时间和小球位置到起点
  t = 0;
  oldTimeStamp = 0;
  ball.x = originX;
  ball.y = originY;
  // 启动动画循环
  animationId = requestAnimationFrame(animate);
}

function animate(timeStamp) {
  // 计算两帧之间的时间差,单位转秒
  const deltaSeconds = oldTimeStamp ? (timeStamp - oldTimeStamp) / 1000 : 0;
  oldTimeStamp = timeStamp;
  // 更新小球坐标
  update(deltaSeconds);
  // 重绘画布
  draw();
  // 小球未落地则继续下一帧
  if(ball.y < canvas.height - ball.radius) {
    animationId = requestAnimationFrame(animate);
  }
}

function update(delta) {
  t += delta;
  // 斜抛运动位移公式
  const offsetX = initialV * Math.cos(initialAngle) * t;
  // 竖直方向位移向上为正,后续转换为Canvas坐标时要做翻转
  const offsetY = initialV * Math.sin(initialAngle) * t - 0.5 * g * t * t;
  // 映射到画布像素坐标
  ball.x = originX + offsetX * scale;
  ball.y = originY - offsetY * scale;
}

function draw() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  // 绘制地面参考线
  context.beginPath();
  context.moveTo(0, canvas.height - 10);
  context.lineTo(canvas.width, canvas.height - 10);
  context.strokeStyle = '#ccc';
  context.stroke();
  // 绘制小球
  context.beginPath();
  context.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);
  context.fillStyle = '#ff4757';
  context.fill();
  context.strokeStyle = '#c0392b';
  context.stroke();
}

// 绑定按钮事件
submitBtn.addEventListener('click', handleSubmit);
resetBtn.addEventListener('click', handleReset);
// 初始状态绘制静止在起点的小球
draw();

HTML部分

<div class="control-group">
  <label>初始速度:</label>
  <input type="range" id="velocityRange" min="5" max="50" value="25">
  <span id="velocityValue"></span> m/s
  <br>
  <label>初始角度:</label>
  <input type="range" id="angleRange" min="10" max="80" value="45">
  <span id="angleValue"></span> °
  <br>
  <button id="submitBtn">开始模拟</button>
  <button id="resetBtn">重置</button>
</div>
<canvas id="canvas" style="border: 1px solid #eee; margin-top: 12px;"></canvas>

效果参考

抛射运动模拟效果示意图

内容的提问来源于stack exchange,提问作者user13285331

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:01:06