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在过渡被中途打断时不会触发。
写法对比
/* 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让它停在最后一帧,但更稳妥的做法是动画结束后用类名或样式把最终状态写到元素上。