3、CSS关键帧动画:@keyframes规则、animation属性、动画填充模式

好,咱们今天来聊聊CSS关键帧动画。说实话,这是我在移动端开发中最喜欢用的动画方式之一。为什么?因为它不需要引入任何第三方库,浏览器原生支持,性能也相当不错。

我记得刚入行那会儿,做动画还全靠JavaScript定时器,一个不小心就卡成PPT。后来CSS动画普及了,我才发现——原来让元素动起来可以这么简单。

3.1 @keyframes规则:定义动画的“剧本”

@keyframes 说白了就是给动画写剧本。你告诉浏览器:从哪开始、到哪结束、中间经过哪些关键位置。浏览器会自动帮你算出中间的所有过渡帧。

来看一个最基本的例子:

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

这里 from 代表 0% 时刻,to 代表 100% 时刻。你也可以用百分比来控制更精细的阶段:

@keyframes bounce {
  0% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-30px);
  }
  100% {
    transform: translateY(0);
  }
}

我在项目中遇到过一个问题:有些同学喜欢把 @keyframes 写在媒体查询里面,结果动画死活不生效。嗯,这里要注意——@keyframes 是全局规则,不能嵌套在 @media 里。

避坑指南:我曾经在某个项目里把 @keyframes 写在了 scoped style 里(Vue 的单文件组件),结果动画只在第一次渲染时有效。后来发现是 scoped 属性给动画名称加了哈希后缀,导致选择器匹配不上。解决方案:把 @keyframes 放到全局样式里,或者用 CSS Modules 的 :global 包裹。

3.2 animation属性:让动画跑起来

有了剧本,还得有导演来喊“Action”。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

我个人习惯这样写简写形式:

.element {
  animation: bounce 1s ease-in-out 0.5s infinite alternate;
}

这个顺序是:名称、时长、缓动函数、延迟、播放次数、方向。你想想看,如果记不住顺序怎么办?我建议你拆开来写,可读性更好:

.element {
  animation-name: bounce;
  animation-duration: 1s;
  animation-timing-function: ease-in-out;
  animation-delay: 0.5s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
小技巧:在移动端,我建议 animation-duration 不要低于 200ms,否则用户根本看不清动画效果。也别超过 1s,太慢会让人觉得卡顿。300ms-600ms 是黄金区间。

3.3 动画填充模式:动画结束后的“归宿”

这个知识点坑很多,我重点讲讲。

animation-fill-mode 控制的是:动画开始前和结束后,元素应该处于什么状态。它有四个值:

  • none(默认):动画结束后回到原始样式
  • forwards:动画结束后保持最后一帧的样式
  • backwards:动画开始前(延迟期间)应用第一帧的样式
  • both:同时应用 forwards 和 backwards 的效果

举个例子你就明白了:

@keyframes slideIn {
  from {
    transform: translateX(-100px);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.box {
  /* 默认 none:动画结束后,元素瞬间回到原始位置 */
  animation: slideIn 1s;
}

.box-forwards {
  /* forwards:动画结束后,元素保持在终点位置 */
  animation: slideIn 1s forwards;
}

我曾经在做一个弹窗动画时踩过坑。弹窗从屏幕外滑入,动画结束后我希望它停在屏幕中央。结果忘了加 forwards,弹窗滑进来又瞬间消失了。排查了半天才发现是 fill-mode 的问题。

重点记忆:如果你希望动画结束后元素保持最终状态,一定要加 forwards。这是移动端开发中最常见的需求之一。

3.4 实战:一个完整的加载动画

咱们把上面学的知识串起来,做一个简单的加载动画:

@keyframes pulse {
  0%, 100% {
    transform: scale(1);
    opacity: 0.5;
  }
  50% {
    transform: scale(1.2);
    opacity: 1;
  }
}

.loading-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #409eff;
  animation: pulse 1.2s ease-in-out infinite;
}

/* 让三个点依次延迟播放 */
.loading-dot:nth-child(2) {
  animation-delay: 0.2s;
}
.loading-dot:nth-child(3) {
  animation-delay: 0.4s;
}

这里用了 infinite 让动画无限循环,配合 animation-delay 实现错落有致的节奏感。说白了就是让三个点像呼吸一样此起彼伏。

性能提示:在移动端做动画,尽量只改变 transform 和 opacity。这两个属性不会触发重排(reflow),GPU 可以直接处理。我见过有人用 left/top 做位移动画,在低端机上掉帧严重。后来改成 translate,流畅度直接起飞。

3.5 多动画叠加

一个元素可以同时应用多个动画,用逗号分隔就行:

.element {
  animation: 
    fadeIn 1s ease-out,
    bounce 0.5s ease-in-out 1s;
}

这样元素先淡入,1秒后开始弹跳。我在做引导页动画时经常用这个技巧,让多个动画串行播放,效果很自然。

好了,关于 CSS 关键帧动画的核心内容就这些。记住:@keyframes 是剧本,animation 是导演,fill-mode 是结局。把这三点吃透了,移动端的大部分动画需求你都能搞定。