果冻震动“僵”因缺乏非线性弹性回弹:需轴向解耦缩放与分段缓动,5帧动画(压扁→撑开→反向抖→收敛)配合cubic-bezier(0.22,0.61,0.36,1)和forwards才显真实Q弹。

为什么直接用 transform: scale() 做果冻震动总显得“僵”
因为果冻感核心不是单纯缩放,而是「非线性弹性回弹」:先快速压扁(scaleY > 1, scaleX 1),最后轻微抖动收敛。纯 scale(0.9) → scale(1.1) → scale(1) 这种线性三帧,物理上像橡皮筋,不是果冻。
关键在两点:轴向解耦(X/Y 缩放独立控制)和缓动函数分段绑定(不能全用 cubic-bezier(.17,.67,.83,.67))。
用 @keyframes 拆出 5 帧实现真实果冻抖动
下面这段动画在 Chrome/Firefox/Edge 均可运行,Safari 需加 -webkit- 前缀(但 Safari 16.4+ 已原生支持):
@keyframes jelly-wiggle {
0% {
transform: scale(1, 1);
}
25% {
transform: scale(0.95, 1.1); /* 压扁 */
}
50% {
transform: scale(1.15, 0.9); /* 横向撑开 + 微压 */
}
75% {
transform: scale(0.98, 1.02); /* 小幅反向抖 */
}
100% {
transform: scale(1, 1);
}
}注意:25% 和 50% 的 scaleY/X 必须反向变化,这是果冻「形变滞后」的视觉来源;75% 不要回到 1,留一点余震才自然。
立即学习“前端免费学习笔记(深入)”;
- 动画时长建议设为
0.45s~0.6s,太短像抽搐,太长像慢动作 - 必须配
animation-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1)(类似ease-out但尾部更缓) - 避免在
:hover直接触发——用户移入移出频繁会导致动画队列堆积,改用animation-play-state: paused/running控制
按钮点击时触发果冻震动的可靠写法
别用 :active,它只在鼠标按下瞬间生效,松手就停,根本播不完 5 帧。正确做法是 JS 绑定 click 事件,加一次 class 触发动画并自动清除:
button.addEventListener('click', function() {
this.classList.add('jelly-shake');
setTimeout(() => {
this.classList.remove('jelly-shake');
}, 600); // 比动画时长多留 100ms 防止中断
});CSS 部分:
.jelly-shake {
animation: jelly-wiggle 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}重点:forwards 保证最后一帧停留,否则按钮会“弹回”原始状态造成视觉跳变;setTimeout 时间必须严格大于动画总时长,否则 Safari 下偶现残留 class 导致下次点不动。
移动端触摸设备的兼容陷阱
iOS Safari 默认会给按钮加「点击高亮灰层」,会盖住果冻动画的形变效果;Android Chrome 则可能因 touch-action: manipulation 抑制部分 transform 动画。
- 必须加
-webkit-tap-highlight-color: transparent去除 iOS 点击蒙层 - 按钮需设
touch-action: none(慎用!会禁用滚动,仅当按钮不位于可滚动容器内时用) - 真机测试时发现动画卡顿?检查是否启用了
will-change: transform—— 它在低端 Android 上反而拖慢渲染,不如直接加transform: translateZ(0)强制 GPU 加速
果冻震动真正的难点不在帧数多少,而在于每一帧的 scaleX/Y 比例差必须控制在 ±0.15 以内,超出人眼立刻识别为“变形失真”,而不是“Q弹”。


















