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。