transition只触发一次的根本原因是浏览器仅在检测到属性值变化且发生重排/重绘起始时启动;连续赋相同值不被视为变化。强制触发需插入中间态,推荐requestanimationframe。

transition 为什么只触发一次?
根本原因在于 CSS transition 只在「属性值发生变化且浏览器能检测到样式重排(reflow)或重绘(repaint)的起始时刻」才启动。如果你用 JS 连续修改同一个属性(比如反复设 element.style.opacity = '0' → '1'),第二次设置时,浏览器发现当前 computed style 已经是 '1',不会触发新的过渡——它不认为这是“变化”,只是“重复赋相同值”。
强制触发下一次 transition 的三种可靠方法
核心思路:让浏览器认为“旧状态确实存在”,即打断连续赋值,插入一个“中间态”。常用手段:
- 用
getComputedStyle强制读取当前值(触发同步 layout),再改值 - 用
setTimeout或requestAnimationFrame把第二次赋值推到下一帧 - 临时移除 class 再加回(适用于 class 控制 transition 属性的场景)
推荐优先用 requestAnimationFrame,它比 setTimeout(fn, 0) 更精准匹配渲染时机:
function triggerTransition(element, prop, value) {
// 先重置为初始值(确保有变化起点)
element.style[prop] = '';
// 强制重排:触发 getComputedStyle
window.getComputedStyle(element)[prop];
// 下一帧再设目标值
requestAnimationFrame(() => {
element.style[prop] = value;
});
}
用 class 切换替代内联 style 更稳定
直接操作 style 容易陷入“JS 覆盖 CSS 规则”的混乱。更健壮的做法是用 class 控制状态,靠 CSS 自身管理 transition:
立即学习“前端免费学习笔记(深入)”;
.box {
opacity: 1;
transition: opacity 0.3s ease;
}
.box.fade-out {
opacity: 0;
}
然后 JS 只负责切换 class:
const box = document.querySelector('.box');
function toggleFade() {
box.classList.remove('fade-out');
// 必须等 class 移除并渲染完成,再加回
requestAnimationFrame(() => {
box.classList.add('fade-out');
});
}
注意:不能写成 box.classList.toggle('fade-out') —— 它无法保证两次调用之间有样式重排间隔,仍会失效。
transition-property 设置太窄也会导致“看似没生效”
常见错误是只写了 transition: all 0.3s,但实际被修改的属性不在可过渡列表里(比如 display、height 从 0 → auto 就不触发)。务必确认:
- 你改的属性是否在
transition-property列表中(显式写比all更安全) - 目标值是否是可动画的(
auto不是数值,display不支持过渡) - 元素是否已挂载且未被
display: none或visibility: hidden阻断渲染流程
例如想过渡 height,必须用具体像素值:height: 0 → height: 200px,不能用 height: auto。
真正卡住的地方往往不是 transition 本身,而是你没给浏览器留出“看到变化”的机会——它需要两个清晰的、有时间差的样式快照。别省那一次 requestAnimationFrame。










