先记住这个答案
用 transition 简写,逗号隔开就行。每组分别写属性名、时长、缓动和延迟,浏览器对每个属性独立计时。不想用简写的话,也可以把 transition-property、transition-duration、transition-timing-function、transition-delay 各写一行,按位置一一对应。两种写法效果一样,简写更常用。
- 逗号分隔多组,每组独立设时长和缓动
- 简写顺序是属性名、时长、缓动、延迟
- 属性数量不匹配时按循环取值(modulo)
all不能拆分时长,要精细控制必须显式列出
简写语法的实际写法
最常见的场景是弹窗出现时:宽度快速展开,透明度慢慢淡入。两个属性需要不同的时长和缓动。
.modal {
width: 0;
opacity: 0;
overflow: hidden;
/* width 用 0.3s ease-out 快速展开 */
/* opacity 用 0.5s linear 慢慢淡入 */
transition: width 0.3s ease-out, opacity 0.5s linear;
}
.modal.show {
width: 400px;
opacity: 1;
}逗号前后各是一组完整的过渡声明。width 在 0.3 秒内以减速曲线展开完毕,opacity 在 0.5 秒内匀速淡入。两者同时开始但不同时结束,视觉上就是「先展开到位,透明度还在慢慢变」。
如果还想让 opacity 延迟 0.1 秒再开始,加上第二个时间值就行:opacity 0.5s linear 0.1s。简写里第一个时间值是时长,第二个是延迟。
长写语法和循环补齐规则
把简写拆开写也完全可以,用在属性比较多的时候更清晰。每个子属性单独一行,按位置和 transition-property 列出的属性一一对应。
.card {
transition-property: transform, opacity, box-shadow;
transition-duration: 0.3s, 0.2s; /* 只写了两个 */
transition-timing-function: ease;
transition-delay: 0s;
}这里 transition-property 列了三个属性,但 transition-duration 只写了两个值。浏览器会循环取值:transform 用 0.3s,opacity 用 0.2s,box-shadow 回到第一个,用 0.3s。transition-timing-function 只写了一个 ease,三个属性都用它。这个循环规则对所有 transition-* 子属性都一样。
- 循环取值引发的 bug
- 理解这个规则能帮你解释一类常见问题:写了三个属性但只写了两个时长,第三个属性的时长不是你预期的,而是循环回去用了第一个。
和 all 的配合
如果其中一组写了 all,它会匹配所有没被前面显式列出的属性。比如 transition: opacity 0.5s, all 0.3s,opacity 用 0.5 秒,其他变化的属性都用 0.3 秒。注意 all 要写在后面,如果写在前面会被后面的具体属性覆盖。不过实际项目里更推荐显式列出每个属性,避免意外的过渡。
容易答错的地方
- 把延迟和时长写反了
transition: opacity 0.1s 0.3s的意思是时长 0.1 秒、延迟 0.3 秒,不是反过来。第一个时间值永远是时长。写反了动画会先等很久才开始,然后一闪而过。- 以为逗号后面的声明会覆盖前面的
- 逗号分隔的是不同属性的过渡配置,不是层叠覆盖。
transition: width 0.3s, opacity 0.5s里两组都生效,不是后面覆盖前面。但如果同一个属性出现两次,后面的确实会覆盖前面的。
面试官还会怎么问?
想让 opacity 先变完再开始变 width,怎么做?
给 width 加延迟。比如 transition: opacity 0.3s, width 0.3s ease 0.3s,opacity 先跑 0.3 秒,结束后 width 的 0.3 秒延迟刚好到期,开始第二段过渡。形成串行效果。
transitionend 会触发几次?
每个参与过渡且值真正变化的属性各触发一次。上面的例子里 width 和 opacity 都变了,就会触发两次 transitionend,通过 event.propertyName 区分。
动画中途改了 transition 声明会怎样?
已经在跑的过渡不会受影响,它用的是过渡启动那一刻的参数。新的 transition 声明要等下次属性值变化时才会生效。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。