h5移动端底部操作栏的物理阻尼过渡需基于时间的弹簧模型实现,通过requestanimationframe结合手势位移与速度实时计算translatey,松手后以当前位移和速度为初值运行阻尼弹簧动画并判断收敛。

在 H5 移动端中,为底部操作栏(如 tabBar、action sheet、bottom drawer)实现带物理阻尼感的呼出/隐藏过渡,核心不是靠 CSS 的 ease-out 或 cubic-bezier 模拟,而是借助 基于时间的弹簧物理模型(Spring Physics),结合手势位移与速度做实时插值计算。原生 Web API 中没有直接的“阻尼弹簧动画”,需手动实现或借助轻量库。
用 requestAnimationFrame + 弹簧公式实时计算位移
关键在于把操作栏的 Y 轴位置(transform: translateY())作为输出,输入是用户拖拽的位移和释放时的瞬时速度,并代入阻尼弹簧方程(如 critically damped spring)求解下一帧位置。
- 选用经典阻尼弹簧公式(如
x(t) = x₀ + (v₀ / ω) × e^(-ζωt) × sin(ω√(1−ζ²)t)),但实际开发中更推荐简化版:使用 react-motion 同源的 spring config(mass, stiffness, damping)+ 数值积分(如 RK4 或 Euler) - 监听
touchmove获取实时deltaY,记录上一帧时间戳与位移,估算当前速度(velocity = (currentY - lastY) / (currentTime - lastTime)) - 松手(
touchend)后,不再跟随手指,而是以当前位移和速度为初值,进入弹簧动画循环:requestAnimationFrame(springStep),每帧更新 translateY 并判断是否收敛(如位移变化
用 CSS @keyframes + transition-timing-function 做轻量近似
若不需要高精度物理反馈(比如只是 tab 切换时的轻微回弹),可用 CSS 配合 JS 控制 class 切换,用自定义贝塞尔曲线模拟“先快后慢再微回弹”的阻尼感:
- 定义两个 class:
.bottom-bar--open(transform: translateY(0))和.bottom-bar--closed(transform: translateY(100%)) - 过渡用
transition: transform 0.45s cubic-bezier(0.28, 0.84, 0.42, 1.1)—— 这个贝塞尔值有轻微过冲(>1 的 y2),能模拟弹性回弹 - 松手时根据速度决定最终状态:若速度向下且位移 > 40px,直接开;若向上且位移
用第三方轻量库快速集成(推荐)
避免重复造轮子,以下库专为 Web 物理动效设计,体积小、无依赖:
-
popmotion(
spring()动画器):支持 mass/stiffness/damping 配置,可中断、可绑定 touch 事件流,output: 'y'直接驱动 transform -
framer-motion(Web 版):虽偏重,但
animate({ y: 0 }, { type: 'spring', stiffness: 300, damping: 20 })一行即可,支持 velocity 输入 -
use-spring (react-spring 的 Web 版本):非 React 项目也可用其核心
@react-spring/core,配合useSpring返回的 ref 手动绑定 DOM
注意移动端特殊适配点
物理动效在 iOS/Android 上表现差异大,需针对性处理:
- iOS Safari 对
transform: translateY()的合成层优化更好,但will-change: transform仍建议加上,避免闪屏 - Android WebView(尤其旧版)对高频
requestAnimationFrame响应延迟明显,建议限制动画最大持续时间(如 800ms 强制结束),并 fallback 到 CSS 过渡 - 禁用页面滚动干扰:操作栏拖拽时
event.preventDefault(),并在touchstart加{ passive: false },同时设置body { overscroll-behavior: none }











