文章

CSS动画与过渡深入解析

transition 与 animation 的区别、缓动曲线怎么选、为什么只动 transform/opacity 才流畅——面试高频考点一篇讲透。

CSS动画与过渡深入解析

一句话概括

CSS 动画不是”让元素动起来”那么简单,它直接关系到页面卡不卡。面试里最常被拎出来问的就是两件事:transition(过渡) 和 animation(动画) 分别怎么用、性能怎么保住。

一句话记住:transition 是”从 A 状态到 B 状态”的平滑变化,必须靠状态切换来触发(hover、加 class、改 style);animation 是”一段独立的多关键帧序列”,页面一加载就能自己跑、能循环、能暂停。它们的底层都能跑在 GPU 合成线程上,所以用对了比手写 JS 动画流畅得多。

核心知识点

1. transition:状态变化的平滑过渡

transition 只描述”某个属性从旧值变到新值”的过程,它自己不会动,必须等元素状态变了才触发(:hover、:focus、JS 改 style、class 切换都算)。

1
2
3
4
5
6
7
8
9
10
.btn {
  background: #42b883;
  transform: scale(1);
  /* 过渡:属性 时长 缓动 延迟 */
  transition: transform 0.2s ease, background 0.3s ease;
}
.btn:hover {
  background: #3aa876;
  transform: scale(1.05);   /* 状态变化 → 触发过渡 */
}

简写顺序是 transition: property duration timing-function delay。面试扣分项:写 transition: all——它会对每一个属性变化都做动画,常常带来意料之外的卡顿,精准列出属性才是好习惯。

2. animation + @keyframes:多阶段动画序列

animation 不依赖状态变化,定义好关键帧就能自己跑,还能循环、正反播、暂停。

1
2
3
4
5
6
7
8
9
@keyframes slideIn {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}

.card {
  /* name  duration  easing  delay  iteration  direction  fill-mode */
  animation: slideIn 0.5s ease-out 0s 1 normal forwards;
}

子属性里面试常考两个:animation-iteration-count: infinite(无限循环,做 loading 必备)和 animation-fill-mode: forwards(动画结束后保留最后一帧,否则会”啪”地跳回初始态)。

3. transition vs animation:到底用哪个

维度transitionanimation
触发方式必须等状态变化加载即运行,可自动播放
关键帧只有开始/结束两态用 @keyframes 定义任意多帧
循环不支持infinite 支持
暂停不支持animation-play-state: paused
正反向只能正向alternate 可往返
典型场景hover 反馈、显隐切换loading、入场动画、持续特效

口诀:“交互反馈用 transition,持续/多段用 animation”。

4. 缓动曲线:别只会写 ease

默认 ease 是”慢-快-慢”,想做出”弹一下”的质感要用三次贝塞尔曲线 cubic-bezier(x1,y1,x2,y2):

1
2
3
4
5
/* ❌ 平铺直叙,没灵气 */
.card { transition: transform 0.3s linear; }

/* ✅ 回弹效果,像被轻轻弹出来 */
.card { transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); }

逐帧动画(精灵图、打字机)则用 steps() 而不是贝塞尔:

1
2
3
4
5
6
7
8
9
.sprite {
  width: 100px; height: 100px;
  background: url('sprite.png');
  animation: run 0.4s steps(4) infinite;   /* 4 帧跳着走 */
}
@keyframes run {
  from { background-position: 0 0; }
  to   { background-position: -400px 0; }
}

5. 性能底线:只动 transform 和 opacity

浏览器渲染有”布局 Layout → 绘制 Paint → 合成 Composite”三个阶段。动画触发的阶段越靠后,越不卡:

1
2
3
4
5
/* ✅ 只触发 Composite(合成器线程),最流畅 */
.box { transform: translateX(100px); opacity: 0.5; }

/* ❌ 触发 Layout(重排)→ 主线程干活,容易掉帧 */
.box { left: 100px; width: 200px; }

transform 和 opacity 的变化只走合成阶段,即使主线程被 JS 卡住,动画依然丝滑。能用 transform: translateX 就别用 left,能用 transform: scale 就别用 width/height。

6. will-change:提前建合成层的开关

will-change 是给浏览器”剧透”:这个属性马上要动,请提前把它提到独立 GPU 图层。

1
2
3
4
5
/* ✅ 只在即将动画的元素、动画期间使用 */
.card { will-change: transform; transition: transform 0.3s; }

/* ❌ 大片元素常驻 will-change → GPU 内存爆掉,反而更卡 */
* { will-change: transform; }

注意:动画结束后最好移除 will-change。现代浏览器里直接写 transform/opacity 动画通常已经会被自动提层,不一定非要手动加 will-change。

其实你每天都在用

  • 按钮 hover 放大/变色:.btn:hover { transform: scale(1.05) } 配上 transition,就是 transition 的日常用法
  • loading 转圈:一个 border + @keyframes rotate { to { transform: rotate(360deg) } } + infinite,经典 animation
  • 骨架屏微光:linear-gradient 背景 + animation: shimmer 左右扫,本质是 background-position 动画
  • 列表入场交错:每个 item 加 animation-delay 递增,配合 :nth-child 做出”一个接一个冒出来”
  • 卡片 3D 翻转:perspective + transform: rotateY(180deg) + transition
  • 消息红点呼吸:box-shadow 大小循环变化,纯 animation 就能做
  • 进度条增长:transition 一改 width 或 --value,进度条就平滑涨上去(注意 width 会重排,预算紧张时改用 scaleX)

常见误解(FAQ)

❌ 误区一:”transition 和 animation 差不多,随便选一个”

差很多。transition 必须等状态变化才动、不能循环、不能暂停;animation 能独立播放、循环、alternate 往返、用 play-state 暂停。交互反馈(hover)用 transition,持续动效(loading、入场)用 animation,这是面试送分题。

❌ 误区二:”用 left/top/width 做位移动画和 transform 没区别”

有本质区别。left/top/width/height 会触发 Layout(重排),整页重新计算布局,主线程干活,动画一大就掉帧;transform: translate/scale 只触发 Composite(合成),在 GPU 上跑,流畅得多。能 translate 就别 left。

❌ 误区三:”will-change 加上去动画一定更流畅”

滥用反而更卡。will-change 会强制创建独立合成层、占用 GPU 显存;给大量元素常驻会让内存爆炸、合成成本飙升。正确做法是只在动画期间、对真正要动的元素使用,动画结束就移除。

❌ 误区四:”CSS 动画性能不如 JS 动画”

正好反过来——只要动画只用 transform/opacity,CSS 动画跑在合成器线程,主线程被 JS 阻塞时它依然流畅,而且代码更短。只有需要”跟随滚动/鼠标位置、逐帧精细控制 canvas”时才上 JS(或 Web Animations API)。

❌ 误区五:”animation 跑完会自动停在最后一帧”

默认不会。fill-mode 默认是 none,动画结束会瞬间跳回初始样式,常见 bug 是”入场动画播完元素消失了”。想要停在结尾就加 animation-fill-mode: forwards(或简写在 animation 里写 forwards)。

一句话总结

transition 管”状态切换的过渡”、animation 管”独立多段序列”,而性能的全部秘密就一句话:让动画只碰 transform 和 opacity,把它丢给 GPU 合成线程——你的页面就丝滑了。

本文由作者按照 CC BY 4.0 进行授权