用requestanimationframe手动实现弹簧阻尼菜单动画:基于胡克定律与阻尼力构建物理模型,每帧用欧拉法更新位移与速度,并实时应用transform;支持拖拽交互与自动回弹,需注意动画终止与内存泄漏防护。

用 requestAnimationFrame 实现带弹性物理反馈的菜单动画,核心是**不依赖 CSS 过渡或第三方库,而是手动模拟简化的弹簧阻尼系统(如胡克定律 + 阻尼力)**,每一帧根据当前位移、速度实时计算下一帧状态,并用 requestAnimationFrame 驱动平滑渲染。
1. 基础物理模型:弹簧-阻尼运动
弹性动画本质是让菜单像连着弹簧一样“弹过去”,再小幅震荡衰减。最简模型用二阶微分方程离散化:
- 加速度 = -k × 位移 - d × 速度(k 是弹性系数,d 是阻尼系数)
- 每帧用欧拉法更新:
速度 += 加速度 × deltaTime位移 += 速度 × deltaTime - deltaTime 取上一帧到当前帧的毫秒差(
performance.now()计算),保证时间精度
2. 菜单 DOM 结构与初始状态
假设菜单是一个 <nav class="menu"></nav>,默认隐藏在屏幕外(如 transform: translateX(-100%)),点击按钮后展开到可视区(translateX(0)):
- 用
getBoundingClientRect()或 CSS 自定义属性(--target-x: 0)设定目标位置 - 维护一个状态对象:
{ x: 当前x位置, vx: 当前x方向速度, targetX: 目标x位置 } - 初始时
x = -menuWidth,targetX = 0,vx = 0
3. requestAnimationFrame 循环与更新逻辑
启动动画时调用一次主循环函数,内部递归调用 requestAnimationFrame,直到速度和位移都足够小(收敛):
let lastTime = 0;
const k = 0.2; // 弹性强度,越大越“硬”
const d = 0.8; // 阻尼系数,0.7~0.9 较自然
<p>function animate(currentTime) {
if (!lastTime) lastTime = currentTime;
const deltaTime = Math.min(currentTime - lastTime, 33); // 限制最大帧差(≈30fps)
lastTime = currentTime;</p><p>const dx = state.targetX - state.x;
const ax = -k <em> dx - d </em> state.vx;
state.vx += ax <em> deltaTime </em> 0.01; // 缩放系数使数值更易调
state.x += state.vx <em> deltaTime </em> 0.01;</p><p>// 应用到 DOM
menu.style.transform = <code>translateX(${state.x}px)</code>;</p><p>// 判断是否停止:位移误差 & 速度都很小
if (Math.abs(dx) translateX(${state.x}px);
return;
}</p><p>requestAnimationFrame(animate);
}</p><p>// 触发:比如点击按钮后
state.targetX = 0;
requestAnimationFrame(animate);
</p>
4. 交互增强:拖拽+释放自动回弹
若想支持手势拖拽菜单(如 swipe 展开/收起),需监听 touchmove 或 mousemove:
- 拖拽中:直接设置
state.x = 当前触摸x - 起始偏移,暂停物理更新 - 松手时(
touchend):根据当前state.x和state.vx(可设为拖拽末速度),自动接续物理动画到最近的目标位置(0 或 -menuWidth) - 用
Math.sign(state.vx)和阈值判断用户意图,提升响应感
不复杂但容易忽略:务必在动画结束或页面隐藏(visibilitychange)时取消 requestAnimationFrame,避免内存泄漏;实际项目中建议封装成类或 hook,复用物理参数和生命周期管理。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











