配了三年动画,今天才发现 cubic-bezier() 一直在骗你——linear() 把弹簧动画从 JS 库里彻底救出来了
你的动画为什么总感觉「不对」?可能是 cubic-bezier() 欺骗了你。
这个问题藏得很深。设计师说「要有弹性」,前端工程师打开 MDN 翻 cubic-bezier(),找到一个 cubic-bezier(0.34, 1.56, 0.64, 1) 觉得差不多能用,然后交差了。界面确实「动了」,但用户总觉得哪里不对——按钮按下去像按在塑料上,弹层弹出来像纸片飘落,切换动画软趴趴的没有重量感。
根因不在代码质量,在于 cubic-bezier() 这个工具本身的数学局限。
cubic-bezier() 只能画 S 曲线,它画不出弹簧
cubic-bezier() 用四个控制点描述一条贝塞尔曲线。这条曲线从 (0,0) 到 (1,1),中间的两个控制点决定了曲率。因为它是多项式函数,曲线不能和自身交叉——也就是说,它只能画出光滑的 S 形,无法出现「冲过去再弹回来」的效果。
这就是为什么你用 cubic-bezier() 做的「弹性动画」永远看起来假。真正的弹簧运动有物理参数:质量(mass)、刚度(stiffness)、阻尼(damping)。当弹簧释放时,它会冲过平衡点,然后振荡几次才停下来。cubic-bezier() 的 S 曲线根本装不下这个过程。
那以前想做一个真正的弹簧动画怎么办?只能引入 GSAP 的 elastic.out()、Framer Motion 的 spring、或者 anime.js 之类的 JS 动画库,在运行时用 JavaScript 模拟物理方程。
2023 年之后,这件事变了。
linear() 的真正用法:它不是线性,它是分段线性插值
很多人看到 linear() 这个名字就跳过了——「线性?那不就是匀速运动吗,丑死了。」
这是一个误解。linear() 的核心能力不是「匀速」,而是「允许值超出 0-1 范围」。
linear() 的语法是:
transition-timing-function: linear(点1, 点2, ..., 点N);
每个点的格式是 值 进度%(进度是可选的)。关键在于:值可以小于 0,可以大于 1。当值超过 1,就产生了回弹/超调(overshoot)——元素会「冲过头」,然后浏览器继续按线性插值往后走,于是就形成了真实的振荡衰减。
看一个具体的弹簧曲线(质量 1、刚度 200、阻尼 15):
:root {
--ease-spring: linear(
0,
0.18,
0.42,
0.72,
0.95,
1.06,
1.04,
1.01,
0.99,
1
);
}
.button:active {
transform: scale(0.95);
transition: transform 0.6s var(--ease-spring);
}
注意这些值:0.95 → 1.06 → 1.04 → 1.01 → 0.99 → 1。动画过程中 transform 先从 0.95 冲到 1.06(超调),然后回落,经过几次振荡最终停在 1。这就是一个真实物理弹簧的压缩-释放过程,全部在 CSS 里完成,运行时零 JavaScript。
你不需要手写这些数字
手调 linear() 的参数不现实。正确的做法是用工具把物理参数转换成 CSS。
推荐工具链:
-
spring.computer(Sam Selikoff):输入 mass/stiffness/damping,导出
linear()曲线。支持预览,导出的代码直接可用。 -
linear-easing-generator.netlify.app(Jake Archibald):输入 CSS
linear()字符串,生成可视化曲线图;也可以反向从曲线推参数。 -
Chrome DevTools:在 Animations 面板里可以直接看到
linear()曲线的可视化,确认超调量是否符合预期。
实际工作中,你不需要理解每个数字的含义。你只需要知道三件事:
- 刚度(stiffness)越高,弹簧越「硬」,振荡越快
- 阻尼(damping)越高,弹簧越快停下来,超调越小
- 质量(mass)越大,弹簧越「重」,响应越慢
打开 spring.computer,调到你满意的弹性效果,复制导出结果到 CSS 里。这就是工程上正确的用法。
真实数据:87% 覆盖,Safari 18+ 完全支持
linear() 函数是 CSS Easing Functions Level 1 规范的一部分,属于 Baseline Widely Available 级别。
各浏览器支持情况(2026 年 8 月):
| 浏览器 | 支持版本 | 稳定版时间 |
|---|---|---|
| Chrome / Edge | 113+ | 2023 年 12 月 |
| Firefox | 112+ | 2023 年 12 月 |
| Safari | 17.2+ | 2024 年 1 月 |
| Safari iOS | 17.2+ | 2024 年 1 月 |
全球覆盖率约 87%。剩余的 13% 主要是很老的移动设备。对于大多数项目,@supports (transition-timing-function: linear(0, 1)) 的渐进增强策略就够了——不支持的浏览器会回退到 ease,动画失去弹性但功能正常。
如果你在用 GSAP 或 Framer Motion 仅仅是为了获得弹簧效果,linear() 是直接的替代方案。迁移路径很简单:
/* 之前 */
transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
/* 之后 */
transition: transform 0.6s linear(
0,
0.009, 0.035 2.1%,
0.141, 0.281 6.7%,
0.723 12.9%,
0.938 16.7%,
1.017, 1.077, 1.121, 1.149 24.3%,
1.163, 1.161, 1.154 29.9%,
1.129 32.8%,
1.051 39.6%,
1.017 43.1%,
0.991, 0.977 51%,
1 100%
);
后者是真正的物理弹簧,前者是「S 形曲线假装有弹性」。
什么时候仍然需要 JS 动画库
linear() 能做弹簧,但不能做所有事。以下场景 JS 动画库仍有优势:
- 物理参数需要动态计算:比如拖拽释放时根据速度计算弹道终点
- 弹性效果需要交互反馈:用户拖动元素,松手后根据拖动方向和速度决定弹到哪里
- 复杂的编排序列:多个动画之间有时序依赖关系
但如果你的场景是「按钮点击反馈」「弹层出现」「卡片切换」这类固定路径的 UI 动效,linear() 完全够用,而且性能更好(纯 CSS,不占用主线程)。
下一步
- 打开 spring.computer,把 stiffness 设为 200、damping 设为 15,看生成的
linear()曲线 - 找一个现有项目里的
cubic-bezier()弹性效果,替换成工具生成的linear()曲线 - 在 Chrome DevTools → Animations 面板里验证曲线的超调量是否符合设计预期
- 加上
@supports渐进增强:.button { transition: transform 0.6s ease; } @supports (transition-timing-function: linear(0, 1)) { .button { transition-timing-function: linear(0, 0.18, 0.42, 0.72, 0.95, 1.06, 1.04, 1.01, 0.99, 1); } }
弹性这件事,以前靠 20-60 KB 的 JS 库才能做到。现在四行 CSS 够了。
评论区
登录后可评论。