CSS transition 进退场用同一个 class 写——进入顺滑、退出拖尾的根因
做轮播卡片的 active 放大效果时,UI 同学说:"进入 active 挺好的,但切走的时候那个缩回去太慢了,感觉黏着的。"改小 duration?进入又变得太仓促。调 easing?两边一起变。折腾半天发现根本改不了——因为压根不知道 CSS transition 是"从哪里读配置的"。
现象
给卡片写了一个 active 状态切换,base class 挂 transition: transform 0.35s ease:
.card {
transform: scale(0.92);
opacity: 0.7;
transition: transform 0.35s ease, opacity 0.35s ease;
}
.card--active {
transform: scale(1);
opacity: 1;
}进退场都是 0.35s,没办法单独调某个方向。想让进入快一点(0.15s)、退出保持余韵(0.35s),无论怎么改 .card 上那一行,两个方向都跟着变。
根因
CSS transition 的工作机制有一个经常被忽视的细节:
transition 规则从目标态(target state)的 computed style 读取,而不是起始态。
具体到 class 切换:
- 进入
.card--active(加 class):浏览器读.card--active的 computed style → transition 规则取自.card--active上的声明 - 退出
.card--active(移除 class):浏览器读.card(base)的 computed style → transition 规则取自.card上的声明
这意味着,transition 声明写在哪个 class 上,就只对"进入那个 class"这个方向生效。
把 transition 写在 .card 上,退出 active 时读 .card,进入 active 时也读 .card(因为 .card--active 没有声明,回落到 base)——结果进退场用同一份配置,想单独改某个方向根本做不到。
正解
把 transition 拆开:进入方向的配置挂在 .card--active,退出方向的配置挂在 .card(base):
Pattern A — 进缓退即时(进入平滑,退出瞬时硬切):
.card {
transform: scale(0.92);
opacity: 0.7;
/* base 不写 transition → 退出 active 时瞬间收回 */
}
.card--active {
transform: scale(1);
opacity: 1;
transition: transform 0.15s ease, opacity 0.15s ease;
/* 只在 active 上写 → 进入时平滑 0.15s */
}Pattern B — 进快退慢(进入锐利,退出保留余韵):
.card {
transform: scale(0.92);
opacity: 0.7;
transition: transform 0.35s ease, opacity 0.35s ease; /* 退出时读这里 */
}
.card--active {
transform: scale(1);
opacity: 1;
transition: transform 0.15s ease, opacity 0.15s ease; /* 进入时读这里 */
}Pattern C — 进退场不同曲线(进入有弹性 overshoot,退出平滑):
.card {
transform: scale(1);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* exit */
}
.card--active {
transform: scale(1.1);
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); /* bouncy enter */
}进入 active 读 .card--active 的 transition,退出 active 读 .card 的 transition。两份声明互不干扰,duration 和 easing 可以完全独立。React / Vue 的 className 切换最终也是 DOM class change,机制完全一致。
排查过程回放
这个坑的排查经历了四个版本:
- 只在 base 挂 0.35s → 进退都 0.35s,进入感觉拖沓
- 把 base 改成 0.15s → 进退都变快,退出太突然,用户反馈"感觉被切断"
- base 删掉 transition,只在 active 挂 0.15s → 进入 OK,退出变成硬切,视觉余韵全没
- base 挂 0.35s + active 挂 0.15s → 进退分离,符合预期
如果一开始知道"transition 从目标态读"这个机制,第四版是直觉答案,不需要试错。
一句话外卖
CSS
transition写在哪个 class 上,就只对"进入那个 class"的方向生效——拆到 base 和 active 两个 class,进退场就能独立配速。