用真实时间驱动运动、按物理公式更新位置、避免帧率依赖是关键:基于requestAnimationFrame的时间戳计算总时间t,代入x=x₀+v₀ₓt和y=y₀+v₀ᵧt−0.5gt²,初速度由speed与angle解析得出,g取980适配像素坐标系。

用 requestAnimationFrame 实现平滑的物理抛物线动画,关键不在于“一帧一算”,而在于**用真实时间驱动运动、按物理公式更新位置、避免帧率依赖**。只要时间步长(delta time)处理得当,哪怕浏览器掉帧,轨迹依然准确自然。
用真实时间差(delta time)代替固定步长
很多人直接每帧加一个固定偏移量(如 y += 0.5),结果动画快慢随帧率浮动——60fps时快,30fps时慢,抛物线严重失真。正确做法是:记录上一帧时间戳,用当前时间减去它得到毫秒级时间差,再换算成秒,代入运动学公式。
- 在
requestAnimationFrame回调中接收时间戳(timestamp),它是 DOMHighResTimeStamp,精度达微秒级 - 首次调用时缓存初始时间(
startTime),后续用delta = (timestamp - lastTime) / 1000得到秒数 - 把
delta作为时间增量,代入位移公式:y = y₀ + v₀ᵧ·t − 0.5·g·t²(注意重力方向为负)
初始化符合物理规律的初速度与参数
抛物线由初速度分解决定。不要凭感觉设 vX=2, vY=-8,而是从发射角度和速率出发计算:
- 给定发射速率
speed = 100(单位:px/s)、仰角angle = 45°(转为弧度:Math.PI / 4) - 水平初速:
v0x = speed * Math.cos(angle) - 竖直初速:
v0y = speed * Math.sin(angle) - 重力加速度建议设为
g = 980(px/s²),这样 1 秒下落约 490px,视觉比例更自然(比默认 9.8 放大 100 倍适配像素坐标系)
在动画循环中同步更新位置与状态
每一帧都应基于**自起始以来的总时间 t** 计算当前位置,而非累加位移——这能消除浮点误差累积,保证轨迹严格遵循抛物线方程:
立即学习“前端免费学习笔记(深入)”;
- 总时间
t = (timestamp - startTime) / 1000 -
x = x0 + v0x * t(水平匀速) -
y = y0 + v0y * t - 0.5 * g * t * t(竖直匀变速) - 检测落地(
y ≥ canvasHeight - ballRadius)后可停止动画或反弹(需更新v0y并重置startTime)
基础代码结构示例
以下是最简可行骨架(省略 canvas 绘制细节):
let startTime = null;
const x0 = 50, y0 = 300;
const speed = 100, angle = Math.PI / 4;
const v0x = speed * Math.cos(angle);
const v0y = speed * Math.sin(angle);
const g = 980;
const ballRadius = 10;
<p>function animate(timestamp) {
if (!startTime) startTime = timestamp;
const t = (timestamp - startTime) / 1000; // 总时间(秒)</p><p>const x = x0 + v0x <em> t;
const y = y0 + v0y </em> t - 0.5 <em> g </em> t * t;</p><p>// 绘制小球:ctx.clearRect(0,0,w,h); ctx.beginPath(); ctx.arc(x,y,ballRadius,...); ...</p><p>// 落地检测(假设画布高 400px)
if (y >= 400 - ballRadius) {
// 可选:停止 or 反弹(v0y *= -0.7; startTime = timestamp; y = 400 - ballRadius;)
return;
}</p><p>requestAnimationFrame(animate);
}
requestAnimationFrame(animate);</p>



















