css原生transition-timing-function无法实现真正弹簧效果,因其仅支持贝塞尔曲线和预设关键字,而弹簧动力学需解二阶微分方程;必须用js逐帧计算并配合css变量或web animations api实现。

用 transition-timing-function 写不出真正的弹簧效果
CSS 原生的 transition-timing-function 只支持贝塞尔曲线(cubic-bezier())和预设关键字(ease-in-out 等),而弹簧动力学(如 iOS 的 spring(1, 80, 10, 0))本质是二阶微分方程解,无法用四点贝塞尔精确表达。强行拟合(比如用 cubic-bezier(0.17, 0.67, 0.83, 0.67))只在特定阻尼/质量组合下“看起来像”,换一个初始位移或目标值就露馅。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 别花时间调
cubic-bezier()参数去“逼近”弹簧——它没有物理参数映射,调试无依据 - 如果只是做简单入场动画,用
ease-out+ 较长持续时间,比硬套假弹簧更自然 - 真需要弹簧响应(比如拖拽回弹、按钮按压反馈),必须引入 JS 计算帧,CSS 只负责应用结果
用 CSS.registerProperty() + @property 控制变量动画时,弹簧逻辑仍在 JS 里
CSS 自定义属性(--spring-value)配合 @property 声明类型后,确实能对变量做插值动画,但它仍依赖 CSS 的时间函数调度,底层没换引擎。也就是说:@property 只让变量可动画,不提供新缓动模型。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 声明时务必写
syntax: "<number>"</number>和inherits: false,否则动画会静默失败 - 更新变量必须用
element.style.setProperty('--spring-value', value),直接改style.cssText会绕过注册,触发不了动画 - 弹簧计算仍得在 JS 里跑:每帧解
x'' + 2ζωx' + ω²x = 0,再把x(t)结果塞进setProperty - 注意浏览器兼容性:
@property在 Safari 16.4+、Chrome 100+ 支持,Firefox 仍不支持
用 requestAnimationFrame 实现弹簧动画时,别手动控制帧率
常见错误是写 setTimeout(..., 16) 或自己计时器,导致动画撕裂、卡顿或耗电。弹簧公式本身对时间精度敏感——哪怕几毫秒误差,多次积分后位移偏差会指数放大。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 严格使用
requestAnimationFrame获取真实渲染时间戳,传给弹簧求解器作为t - 弹簧求解器别用欧拉法(
x += v * dt),改用更稳定的 Velocity Verlet:先更新速度半步,再更新位置,最后更新速度另半步 - 加阻尼项时,系数别硬编码:用
ζ = damping / (2 * Math.sqrt(stiffness))这类带物理意义的表达式,方便后期调节 - 动画结束判断别用
Math.abs(v) ,改用位移变化量 + 时间衰减阈值,避免在极小振动时无限循环
用 Web Animations API 替代 transition 时,关键不是替换,是放弃 CSS 时间轴
有人以为换成 element.animate(...) 就能“接管”缓动,其实不然:animate() 的 easing 选项仍是 cubic-bezier() 或关键字。真正有用的是它的 updatePlaybackRate 和逐帧回调能力,但这些依然不解决弹簧建模问题。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 不要给
animate()配easing,直接设easing: "linear",把所有时间逻辑交给 JS - 用
animation.onfinish做清理,但别依赖它判断弹簧停止——它只反映 CSS 动画结束,和物理状态无关 - 若需暂停/拖拽,用
animation.pause()+ 手动记录当前currentTime,再喂给弹簧求解器重算初始条件 - 性能敏感场景(如列表滚动中每个 item 都有弹簧),务必用
will-change: transform提前升层,否则每次重排布局开销巨大
弹簧不是缓动函数,是实时求解的物理过程。最易被忽略的一点:你写的每个 requestAnimationFrame 回调里,都要重新计算当前时刻的速度与加速度,而不是只算一次位移。漏掉这步,动画就会“滑”出去,停不住。










