纯css实现真实弹跳动画需分5阶段手动调参:下落→触地→压缩→回弹→微晃→静止;关键在非对称cubic-bezier缓动、y轴缩放配合transform-origin:bottom、各段时长按能量衰减比例分配,并禁用pointer-events防点击冲突。

纯 CSS 实现“带重力下落 + 弹性碰撞”的图标动画,本质上是用 @keyframes 模拟物理过程,但必须接受一个事实:CSS 没有真实物理引擎,所有“重力感”和“弹性”都靠关键帧位置、时长、缓动函数(cubic-bezier)和变形组合硬凑出来。想接近真实,就得手动拆解下落→触地→压缩→回弹→微晃→静止这 5 个阶段。
为什么不能只用 ease-in-out + translateY?
因为 ease-in-out 是对称缓动,而真实下落加速度递增、碰撞后回弹衰减明显——它无法表达“越弹越矮、越晃越慢”的能量损耗。常见错误是动画看起来像弹簧振子(等幅来回),而不是落地小球(振幅快速衰减)。
- 单纯
transform: translateY()只有位移,缺少触地瞬间的视觉反馈(如轻微压扁) - 不控制各段时长比例(比如触地压缩帧必须极短,约 5%~8% 动画周期),就会失去“砰”的实感
- 忽略 Y 轴缩放(
scaleY)和轻微水平晃动(translateX),会让动画显得僵直
关键帧怎么分段才像真球落地?
以 1.2s 总时长为例,推荐按能量衰减节奏切分(不是均匀百分比):
-
0%:起始高位,transform: translateY(-120px) scaleY(0.95)(预压一点,暗示即将下坠) -
35%:触地瞬间,transform: translateY(0) scaleY(0.75)(快速下压+明显压扁) -
42%:第一次回弹顶点,transform: translateY(-40px) scaleY(1.05)(略高于原位,带过冲) -
60%:二次触地,transform: translateY(0) scaleY(0.9)(压缩变轻,幅度只剩第一次的 1/3) -
75%:微晃停顿,transform: translateY(0) scaleX(1.02) scaleY(0.98)(左右微倾,模拟惯性) -
100%:完全静止,transform: translateY(0) scale(1)
注意:所有 scaleY 值都要配合 transform-origin: bottom center,否则压扁中心会偏移,像被踩扁的橡皮鸭。
缓动函数必须手写 cubic-bezier
浏览器默认的 ease 或 ease-in-out 完全不够用。每一段的关键帧之间,要用不同贝塞尔曲线控制节奏:
- 下落段(0% → 35%):用
cubic-bezier(0.33, 0, 0.22, 0.78)—— 先慢后快,模拟重力加速 - 触地压缩(35% → 42%):用
cubic-bezier(0.6, 0.04, 0.98, 0.34)—— 极快收束,制造“砰”感 - 回弹段(42% → 60%):用
cubic-bezier(0.2, 0.8, 0.4, 1)—— 过冲回弹,带弹性 - 后续衰减段:统一用
cubic-bezier(0.25, 0.46, 0.45, 0.94),让幅度逐次收敛
别试图用单个 animation-timing-function 覆盖全程——CSS 不支持关键帧级缓动,只能靠多段 @keyframes + 手动插值逼近。
移动端点击触发时要注意什么?
如果想让图标在 :active 时播放一次下落弹跳(而非循环),得特别处理:
- 把动画设为
animation: bounce-once 1.2s forwards,forwards确保停在最终态 - 必须加
pointer-events: none到动画中的图标元素,否则快速连续点击会卡住动画状态 - 避免在
:active里直接写transform,会和@keyframes冲突;改用 class 切换,例如.icon.is-bouncing - 安卓 WebView 下,
transform若没触发硬件加速,可能掉帧——补上transform: translateZ(0)或will-change: transform
真正难的不是写出第一版弹跳,而是调出“让人下意识觉得‘这球真会弹’”的那几帧毫秒级偏移和缩放比例。多花 10 分钟调 scaleY(0.74) 和 scaleY(0.76) 的差别,比堆砌 10 个关键帧更有效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











