心跳动画应使用三段式@keyframes:0%静止→瞬时膨胀至scale(1.3)→回弹至scale(1.05)→缓落回scale(1),配合cubic-bezier(0.2,0.6,0.4,1)缓动、1.2s周期、transform-origin:center及backface-visibility:hidden优化渲染。

用 @keyframes 定义心跳缩放曲线,别用线性过渡
真实的心跳不是匀速放大缩小,而是快速收缩 + 缓慢回弹。直接写 0% { transform: scale(1); } 100% { transform: scale(1.2); } 会显得机械。应该拆成三段:初始静止 → 瞬间膨胀(scale(1.3))→ 回弹到略大于原始尺寸(scale(1.05))→ 缓慢落回 scale(1)。
关键点在于缓动函数:animation-timing-function: cubic-bezier(0.2, 0.6, 0.4, 1) 比 ease-in-out 更贴合生理节奏;同时把动画周期控制在 1.2s 左右,接近成人静息心率(50–70bpm)。
常见错误是把 transform: scale() 和 opacity 绑定在同一关键帧里同步变化——这会让“脉冲感”变弱。建议只动缩放,颜色/透明度保持恒定,靠视觉暂留形成搏动感。
给元素加 animation 时必须设 transform-origin: center
默认 transform-origin 是 50% 50%,但某些旧版 Safari 或嵌套 flex 容器中会意外偏移,导致缩放中心漂移,出现“抖动”而非“搏动”。显式声明可杜绝这类兼容性问题。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- 所有使用该动画的元素都加上
transform-origin: center; - 如果元素本身有
border-radius,确保它不为0(比如设border-radius: 50%),否则尖角缩放时边缘像素会闪烁 - 避免在动画元素上同时使用
will-change: transform—— 现代浏览器已自动优化,强行触发反而增加合成层开销
实现“无限”要靠 infinite + animation-play-state 控制启停
animation-iteration-count: infinite 是基础,但真正容易被忽略的是交互响应:用户 hover 或 loading 结束时需暂停。直接改 animation-play-state: paused 即可,无需重写整个 animation 声明。
注意两个坑:
- 不要用
display: none隐藏动画元素——它会中断动画状态,再显示时从头开始,破坏节奏连续性;改用visibility: hidden或opacity: 0 - 如果多个心跳指示器共存(比如列表项),别给它们同一份
animation-delay,否则全屏同频跳动很假;可用 CSS 自定义属性动态设延迟:--pulse-delay: calc(var(--index) * 0.1s)
移动端真机测试时,transform: scale() 可能触发模糊
iOS Safari 和部分安卓 WebView 在高缩放倍率(如 scale(1.3))下会对元素做亚像素渲染,导致边缘发虚。这不是 bug,是浏览器为性能做的妥协。
缓解方案有限但有效:
- 把缩放上限降到
scale(1.22),肉眼几乎无差别,但模糊显著减轻 - 加一行
backface-visibility: hidden;强制启用硬件加速层(注意:仅对支持 3D transform 的设备生效) - 不用
border而用box-shadow模拟描边,后者在缩放时更稳定
最麻烦的情况是动画运行时用户切换标签页再切回——Safari 可能丢帧导致首次跳动延迟。目前无完美解法,只能接受这是 Web 动画的固有边界。


















