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
相关产品推荐
相关产品推荐

