2、CSS关键帧动画:深入理解@keyframes规则,掌握animation系列属性,制作循环与非线性动画。

好,咱们接着聊CSS动画。上一章我们把transition聊透了,它适合做「从A到B」的简单过渡。但如果你想让一个元素做一套完整的动作——比如先向右、再向下、再旋转、再弹回来——那transition就有点力不从心了。

这时候,就该@keyframes登场了。

2.1 @keyframes 规则:动画的剧本

@keyframes 说白了就是给动画写剧本。你告诉浏览器:「在0%的时候元素在哪儿,在50%的时候长什么样,在100%的时候变成什么。」浏览器就会自动帮你算出中间的所有过程。

基本语法长这样:

@keyframes 动画名称 {
  0% {
    /* 起始状态 */
    transform: translateX(0);
    opacity: 1;
  }
  50% {
    /* 中间状态 */
    transform: translateX(100px);
    opacity: 0.5;
  }
  100% {
    /* 结束状态 */
    transform: translateX(0);
    opacity: 1;
  }
}

我个人习惯用百分比来定义关键帧,因为这样最灵活。不过你也可以用 fromto 关键字,它们分别等价于 0% 和 100%。

小技巧: 如果你只需要起始和结束两个状态,用 from/to 更简洁。但一旦超过两个关键帧,我建议统一用百分比,可读性更好。

2.2 animation 系列属性:让动画跑起来

光有剧本不行,你还得告诉浏览器怎么演。这就是 animation 系列属性的作用。

我整理了一张表,把常用的 animation 属性列出来:

属性 作用 默认值
animation-name 指定要使用的 @keyframes 名称 none
animation-duration 动画完成一个周期的时间 0s
animation-timing-function 动画的速度曲线 ease
animation-delay 动画开始前的延迟时间 0s
animation-iteration-count 动画播放次数 1
animation-direction 动画播放方向 normal
animation-fill-mode 动画结束后的状态 none
animation-play-state 控制动画播放或暂停 running

写完整写法是这样的:

.box {
  animation-name: bounce;
  animation-duration: 2s;
  animation-timing-function: ease-in-out;
  animation-delay: 0.5s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  animation-fill-mode: both;
}

当然,你也可以用简写:

.box {
  animation: bounce 2s ease-in-out 0.5s infinite alternate both;
}
注意: 简写虽然方便,但顺序容易搞混。我的建议是——刚开始学的时候用分开写,等熟练了再用简写。我曾经在项目里因为简写顺序写错,排查了半天才发现是 animation-delay 被吞了。

2.3 循环动画:让动画永不停歇

做循环动画很简单,把 animation-iteration-count 设为 infinite 就行。

但这里有个坑——循环动画的衔接。如果你想让动画看起来是「无缝循环」的,那起始状态和结束状态必须一致。否则每次循环结束都会「跳」一下。

举个例子:

@keyframes loop-bad {
  0% { transform: translateX(0); }
  100% { transform: translateX(100px); }
}

@keyframes loop-good {
  0% { transform: translateX(0); }
  50% { transform: translateX(100px); }
  100% { transform: translateX(0); }
}

第一个动画每次循环结束都会从 100px 跳回 0px,看起来很突兀。第二个动画在 100% 时回到了 0px,所以循环起来是平滑的。

核心要点: 无缝循环的关键——起始状态 = 结束状态。如果做不到,可以用 animation-direction: alternate 让动画来回播放,也能达到类似效果。

2.4 非线性动画:让动作更自然

现实世界中的运动很少是匀速的。你扔一个球,它先加速再减速。你推一个抽屉,它先快后慢。这就是非线性动画的魅力。

在 CSS 里,我们用 animation-timing-function 来控制速度曲线。除了内置的 easelinearease-inease-outease-in-out,你还可以用 cubic-bezier() 自定义曲线。

我常用的几个贝塞尔曲线:

/* 弹跳效果 */
cubic-bezier(0.68, -0.55, 0.27, 1.55)

/* 先慢后快再慢 */
cubic-bezier(0.42, 0, 0.58, 1)

/* 快速进入,缓慢结束 */
cubic-bezier(0.25, 0.1, 0.25, 1)

你想想看,为什么有些动画看起来「很舒服」,有些却「很生硬」?区别就在这。生硬的动画往往是匀速的,而舒服的动画一定是有节奏变化的。

我的经验: 做弹跳效果时,cubic-bezier 的 y 值可以超出 0-1 范围。比如 (0.68, -0.55, 0.27, 1.55) 中的 -0.55 会让动画先反向运动,产生「蓄力」的感觉。这个技巧我在做按钮点击反馈时经常用。

2.5 实战:制作一个呼吸灯效果

光说不练假把式。咱们结合上面讲的知识,做一个呼吸灯效果。

@keyframes breathe {
  0% {
    transform: scale(1);
    opacity: 0.6;
    box-shadow: 0 0 10px rgba(0, 150, 255, 0.3);
  }
  50% {
    transform: scale(1.2);
    opacity: 1;
    box-shadow: 0 0 30px rgba(0, 150, 255, 0.8);
  }
  100% {
    transform: scale(1);
    opacity: 0.6;
    box-shadow: 0 0 10px rgba(0, 150, 255, 0.3);
  }
}

.light {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: #0096ff;
  animation: breathe 3s ease-in-out infinite;
}

这个效果看起来就像灯在「呼吸」——慢慢变亮变大,再慢慢变暗变小。注意我用了 ease-in-out,这样变化过程是平滑的。如果用 linear,就会显得很机械。

避坑指南: 我曾经在项目里用 animation 做 loading 动画,结果在低端手机上卡成 PPT。后来发现是同时动画的属性太多了——transform、opacity、box-shadow 一起动,GPU 扛不住。建议:优先动画 transform 和 opacity,这两个属性 GPU 有硬件加速,性能最好。

2.6 小结

嗯,这一章的内容就到这。咱们回顾一下:

  • @keyframes 是动画的剧本,用百分比定义关键帧
  • animation 系列属性 控制动画的播放方式
  • 循环动画 要注意起始和结束状态的一致性
  • 非线性动画 用 timing-function 和 cubic-bezier 实现自然运动

下一章咱们聊聊 CSS 过渡与动画的性能优化。我会分享一些我在实际项目中踩过的坑,以及怎么让动画跑得更流畅。到时候见。