3、CSS动画入门:transition过渡、animation动画、关键帧@keyframes、动画性能分析

好,咱们今天聊聊CSS动画。说实话,CSS动画是我个人觉得性价比最高的动效实现方式——你不需要引入任何第三方库,浏览器原生支持,写起来也简单。但很多人用着用着就踩坑了,尤其是性能问题。

我刚开始做大屏项目时,就吃过这个亏。一个简单的旋转动画,CPU直接飙到80%,老板站我身后看了三秒,说了句「这页面怎么跟PPT似的卡」。嗯,从那以后,我再也不敢小看CSS动画的性能分析了。

3.1 transition过渡:最简单的动效入口

transition 说白了就是「状态变化时的平滑过渡」。你想想看,一个按钮从红色变成蓝色,如果没有过渡,它「啪」一下就变了,很生硬。加上过渡,它会在你指定的时间内渐变过去。

基本语法长这样:

/* 简写形式 */
transition: property duration timing-function delay;

/* 实际例子 */
.box {
  width: 100px;
  height: 100px;
  background: #409EFF;
  transition: all 0.3s ease;
}

.box:hover {
  width: 150px;
  background: #E6A23C;
}

这里有几个点要注意:

  • property:指定哪些属性要过渡。用 all 省事,但性能差一些。我建议只写你需要的属性。
  • duration:持续时间。大屏动效一般 0.3s~0.6s 比较舒服,太快了看不清,太慢了显得拖沓。
  • timing-function:缓动函数。默认 ease,但 cubic-bezier() 自定义曲线才是大屏动效的灵魂。
我的小技巧: 大屏上的过渡效果,我习惯用 cubic-bezier(0.25, 0.1, 0.25, 1),这个曲线开头快、结尾稳,视觉上很「高级」。你可以试试看。

3.2 animation动画与@keyframes关键帧

transition 只能处理「开始→结束」两个状态。但如果你想要更复杂的动画——比如先放大、再旋转、最后缩小——那就得上 animation + @keyframes 了。

我举个例子,一个呼吸灯效果,大屏上经常用来表示「数据在更新」:

@keyframes breathe {
  0% {
    opacity: 0.4;
    transform: scale(0.95);
  }
  50% {
    opacity: 1;
    transform: scale(1.05);
  }
  100% {
    opacity: 0.4;
    transform: scale(0.95);
  }
}

.dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #67C23A;
  animation: breathe 2s ease-in-out infinite;
}

这里的关键帧 @keyframes 定义了动画的「剧本」,而 animation 属性负责「播放」。常用的 animation 子属性有:

属性 说明 我的建议值
animation-name 关键帧名称 见名知意,比如 fadeIn
animation-duration 单次播放时长 大屏推荐 1s~3s
animation-timing-function 缓动函数 尽量用 ease-in-out
animation-iteration-count 播放次数 infinite 或具体数字
animation-direction 播放方向 alternate 来回播放很常用
animation-fill-mode 结束状态保持 forwards 保留最后一帧
我曾经踩过的坑:animation: myAnim 1s infinite 时,如果页面切到后台再切回来,有些浏览器会「跳帧」——动画直接跳到中间状态。解决方案是加上 animation-play-state: paused 配合 visibilitychange 事件手动控制。嗯,这个坑我记了好几年。

3.3 动画性能分析:为什么你的动画会卡?

好,这是今天的重点。很多同学问我:「为什么同样的动画,别人的页面跑60帧,我的就掉到20帧?」

原因其实很简单——你触发了浏览器的重排(reflow)

浏览器渲染一个页面,大致分三步:

  1. Layout(布局):计算元素的位置和大小
  2. Paint(绘制):把元素画出来
  3. Composite(合成):把图层拼在一起

如果你用 lefttopwidthheight 这些属性做动画,每次变化都会触发 Layout + Paint + Composite,三件套全上,性能自然差。

那用什么属性做动画性能最好?答案是:transform 和 opacity

这两个属性只触发 Composite,不触发 Layout 和 Paint。浏览器会把它们交给 GPU 处理,帧率稳稳的 60fps。

/* ❌ 性能差 */
.box {
  left: 0;
  transition: left 0.3s;
}
.box.active {
  left: 100px;
}

/* ✅ 性能好 */
.box {
  transform: translateX(0);
  transition: transform 0.3s;
}
.box.active {
  transform: translateX(100px);
}

我做过一个测试:同样一个 200 个元素的列表动画,用 left 做,帧率只有 18fps;换成 transform,直接飙到 58fps。差距就是这么明显。

3.4 实战:大屏数据卡片入场动画

咱们结合前面讲的知识,写一个实际的大屏场景——数据卡片依次入场。

@keyframes cardEnter {
  from {
    opacity: 0;
    transform: translateY(30px) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.card {
  opacity: 0; /* 初始隐藏 */
  animation: cardEnter 0.6s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
}

/* 依次延迟入场 */
.card:nth-child(1) { animation-delay: 0s; }
.card:nth-child(2) { animation-delay: 0.15s; }
.card:nth-child(3) { animation-delay: 0.3s; }
.card:nth-child(4) { animation-delay: 0.45s; }

这里用了 forwards 保持结束状态,配合 animation-delay 实现「波浪式入场」。视觉上很有节奏感,而且性能很好——因为只用了 opacitytransform

性能优化总结:
  • 能用 transform 就别用 left/top
  • 能用 opacity 就别用 display/visibility
  • 动画元素尽量单独一层(加 will-change: transform
  • 大屏上避免同时触发太多动画,控制在 10~15 个以内

最后说一句:CSS动画虽然简单,但想做好、做流畅,还是得理解浏览器渲染原理。你想想看,如果连 transformleft 的性能差异都不清楚,那优化起来就只能靠「试」了——这可不是工程师该有的做法。

下一章咱们聊 JavaScript 动画,到时候会结合 requestAnimationFrame 做更精细的控制。嗯,那才是真正考验功底的地方。