CSS 中,transition 与 animation 通常不能同时作用于同一个属性。原因在于 CSS 层叠规则只会保留最终声明值,而 animation 一旦运行,就会接管该属性的控制权,导致 transition 的过渡插值失效;如果再配合 animation-fill-mode: forwards,还会让过渡动画丢失初始值。

transition 和 animation 为什么不能共用同一属性
按照 CSS 动画与过渡的规范,在同一选择器里,若针对同一 CSS 属性存在多种控制方式,浏览器最终只会以当前生效的控制逻辑为准。transition 和 animation 都会接管目标属性,例如 transform、opacity 等;当 animation 启动后,浏览器会认为该属性已经“由动画托管”,因此 transition 的过渡计算会被直接忽略。这不是兼容性问题,也不是 bug,而是标准的 CSS 行为。
animation-fill-mode: forwards 是最容易被忽视的冲突来源
当 animation-fill-mode 设置为 forwards(或 both)时,动画播放结束后,元素会停留在最后一帧对应的属性值上。此时如果再通过 hover、class 切换或 JavaScript 修改同一个属性,transition 往往无法正确获取“起始状态”——因为它读取到的是动画残留的最终值,而不是你原本写在 CSS 里的初始值,所以最终表现就是样式瞬间切换,没有平滑过渡效果。
- 典型现象:
.box { animation: slideIn 1s forwards; transition: transform 0.3s; }+:hover { transform: rotate(180deg); }→ hover 时旋转不会缓动,而是直接跳到目标状态 - 排查方式:去掉
forwards,或者临时改成none,通常就能立刻恢复 hover 过渡效果 - 补充说明:
backwards也有类似风险——它会在动画开始前提前应用from状态,从而覆盖你原先定义的初始样式
多个 transition 声明互相覆盖导致“部分失效”
很多前端开发者会误以为写两行 transition 能叠加多个过渡效果,实际上后面的声明会直接覆盖前面的内容:
div {
transition: width 0.3s;
transition: height 0.4s; /* ← width 的过渡已丢失 */
}正确写法应该合并到同一条声明中:
div {
transition: width 0.3s, height 0.4s, opacity 0.2s;
}- 尽量不要直接使用
transition: all 0.3s,因为它会让所有可变化的属性都参与过渡,包括一些你未必想动画化的属性,如box-shadow、z-index等,容易带来卡顿或异常抖动 - 优先选择更利于性能的属性:建议把
transition或animation主要用于transform、opacity,尽量避免频繁操作width、height、top这类会触发重排的属性
想共存?必须严格隔离作用范围
如果确实希望 CSS transition 与 animation 同时使用,最稳妥的方法就是让它们不要控制同一个属性:
transition负责交互反馈:例如按钮 hover 时的opacity变化,或transform: scale()的缩放过渡animation负责持续性动效:例如transform: rotate()的无限旋转,或background-position的背景滚动动画- 如果必须操作同一类属性(例如都涉及
transform),就需要额外包一层 DOM:外层元素使用transition控制位移或缩放,内层元素使用animation控制旋转,这样通过结构隔离来避免冲突
真正难处理的并不是 CSS 语法本身,而是动画结束后样式状态是否足够“干净”。只要 animation 残留了最终值,尤其是在 forwards 锁定状态的情况下,transition 就很难再正确识别过渡起点,这也是 CSS 动画和过渡冲突最常见的根本原因。
