前端知库

transition 和 animation 有什么区别?

transition 在属性值发生变化时,把旧值到新值的过程补成动画,只有起点和终点两个状态,需要 :hover、改类名这样的触发。animation 用 @keyframes 定义多个关键帧,元素应用后自动播放,可以设置循环次数、方向、暂停和结束后是否保留样式。两者对应的事件也不同。

参考答案

简答

按钮悬停时颜色慢慢变深,用 transition;加载图标一直转圈,用 animation。这两个例子基本就是两者的分工。

transition 管的是“变化的过程”。属性值本来会瞬间从 A 变成 B,加了 transition 以后,浏览器在设定的时长里把中间值补出来。所以它必须有一次值的变化来触发,比如进入 :hover、切换类名,而且只有起点和终点两个状态。

animation 管的是“一段写好的动画”。用 @keyframes 定义起点、终点和中间任意多个关键帧,元素一应用 animation 就开始播放,不需要状态变化。它还能设置播放次数(包括无限循环)、方向、暂停,以及开始前和结束后要不要保留关键帧的样式。

两者都会触发事件:transition 有 transitionrun、transitionstart、transitionend,animation 有 animationstart、animationiteration、animationend。要注意 transitionend 在过渡被中途打断时不会触发。

写法对比

CSS
/* transition:值变化时补出过程 */
.button {
  background: #3b82f6;
  transition: background-color 200ms ease;
}
.button:hover {
  background: #1d4ed8;
}

/* animation:关键帧定义好过程,应用后自动播放 */
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
.loading {
  animation: spin 1s linear infinite;
}

.button 平时不会动,鼠标移上去,背景色的值从一个颜色变成另一个,transition 才把这 200ms 的过程画出来;鼠标移开,值变回去,又会反向过渡一次。

.loading 不需要任何触发,元素一有这个类就开始转,infinite 让它一直循环。MDN 的说明是,CSS 动画由两部分组成:描述动画的样式,以及表示起点、终点和可能的中间点的一组关键帧。

为什么刚插入的元素 transition 不生效

一个常见的坑:用 JavaScript 创建元素,插入页面后马上改样式,结果没有过渡效果,直接跳到了终点。原因是浏览器还没为这个元素算过一次样式,它没有“旧值”,也就谈不上从旧值过渡。

用 headless Chrome 实测,新建元素插入后立刻改 width,getAnimations() 得到 0 个动画;先读一次 getComputedStyle(el).width 再改,就得到 1 个过渡。更现代的做法是用 @starting-style 声明进入时的起始样式,见《@starting-style 和离散属性过渡解决了什么问题?》。animation 没有这个问题,因为关键帧本身就给出了起点。

两者的能力差在哪里

animation 的子属性比 transition 多。animation-iteration-count 控制播放次数,animation-direction 控制每一轮正着放、倒着放还是交替,animation-play-state 可以暂停和继续,animation-fill-mode 决定动画开始前、结束后要不要应用关键帧里的样式。transition 只有属性、时长、缓动函数、延迟,以及控制离散属性的 transition-behavior。

所以只是两个状态之间的切换,比如悬停、展开收起、显示隐藏,用 transition 更简单,状态由类名控制,逻辑清楚。需要多个阶段、循环或者页面一加载就播放的效果,用 animation。

用 JavaScript 逐帧计算的 requestAnimationFrame,以及改哪些属性动画更流畅,见《transition、animation 和 requestAnimationFrame 怎么选?》。

常见追问

transitionend 为什么有时不触发?

MDN 说明,过渡在完成之前被中止时 transitionend 不会触发,比如元素被设成 display: none,或者过渡中的属性值又被改了。另外 CSS Transitions 规范规定,每个发生过渡的属性都会单独派发事件,一次过渡多个属性就会收到多次 transitionend,处理时要看 event.propertyName。

animation 结束后为什么样式又跳回去了?

默认情况下,动画结束后关键帧的样式就不再应用,元素回到自己本来的样式。可以用 animation-fill-mode: forwards 让它停在最后一帧,但更稳妥的做法是动画结束后用类名或样式把最终状态写到元素上。