根本原因是浏览器首帧渲染时未获取到可插值的明确起始值,如opacity未显式声明初始值,导致从auto/undefined→目标值无法插值;transition必须写在基础选择器中且属性需有确定初始值。

transition 为什么在首次 class 添加时静默失败
根本原因不是 transition 写错了,而是浏览器在首帧渲染时,若元素的可过渡属性(如 opacity、transform)没有显式初始值,就无法确定「起始状态」,导致过渡直接跳到终态。比如 #box { transition: opacity 0.3s; } 却没写 opacity: 0;,那 JS 执行 el.classList.add('show') 后,浏览器看到的是从 opacity: auto(实际计算为 1)→ 1,变化量为 0,自然不触发。
必须在 CSS 中声明明确的初始值
transition 能否启动,取决于「第一帧中该属性是否有确定的、可插值的数值」。没有初始值,就没有起点。
-
opacity必须写opacity: 0;或opacity: 1;,不能依赖继承或未声明状态 -
transform必须写transform: translateY(0);,不能只靠目标类里的translateY(100px) -
max-height可替代height: auto,但初始值必须是具体数字,如max-height: 0;,终态写max-height: 500px; - 用
display: none隐藏的元素,transition 完全失效——改用visibility: hidden; opacity: 0;+transition: opacity 0.3s;
requestAnimationFrame 不是万能解药,它只解决“时机”,不补“缺失的起点”
很多人以为加了 requestAnimationFrame 就万事大吉,其实它只确保 class 添加被推到下一帧执行,从而让浏览器有时间把初始样式“拍下来”。但如果初始值本身不存在,下一帧依然没东西可过渡。
- 错误写法:
el.classList.add('show'); // 初始值缺失,加 rAF 也白搭 - 正确结构:
el.classList.remove('hidden'); // 确保初始类含 opacity: 0 等<br>requestAnimationFrame(() => {<br> el.classList.add('show'); // 延迟到下一帧添加终态类<br>}); - 注意:
requestAnimationFrame回调里不要读取offsetHeight或getComputedStyle,这会强制同步 layout,反而可能打断过渡队列
容易被忽略的隐藏陷阱:transition 声明的位置和 scope
即使初始值写了,transition 仍可能失效——因为声明位置错了。
- ❌ 错误:只在目标类里写
.show { transition: opacity 0.3s; opacity: 1; }→ 浏览器认为 transition 是“变化后才加的”,不参与本次过渡 - ✅ 正确:在基础类或元素默认选择器中声明,例如
#box { opacity: 0; transition: opacity 0.3s; },再用.show { opacity: 1; } - 如果用了 Unocss / Windi CSS,确认
transition-类(如transition-opacity)和duration-类(如duration-300)同时存在,否则生成的 CSS 中transition值为空字符串 - Safari 下,父元素若有
transform: translateZ(0)或will-change: transform,可能截断子元素 transition,临时移除可验证
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











