根本原因是transition未写在基础类中且目标属性缺显式初始值;必须将transition声明于默认样式(如.box{transition:opacity 0.3s;opacity:0;}),并确保动态元素插入前已具备完整过渡上下文。

根本原因不是 CSS 写错了,而是浏览器压根没拿到「起始快照」——过渡需要明确的起点和终点,而初次渲染时起点缺失或未被采样。
transition 声明不在基础类中
浏览器只在属性值变化时触发过渡,但前提是 transition 必须从变化前就存在。如果只写在 .active、:hover 或其他状态类里,第一次变化就是突变。
- ❌ 错误:
.box.active { opacity: 1; transition: opacity 0.3s; }—— 类加上去那一刻才声明 transition,来不及参与本次变化 - ✅ 正确:
.box { opacity: 0; transition: opacity 0.3s; }+.box.active { opacity: 1; } - 验证方式:打开 DevTools → Elements → Computed 面板,搜索
transition-property,确认它在元素“常态”下就存在
目标属性没有显式初始值
过渡插值依赖两个确定数值。若属性初始值是 auto、initial 或未声明(如 left、height、transform),浏览器无法计算中间帧,直接跳到终态。
- opacity 必须写
opacity: 0;,不能只靠.show { opacity: 1; } - transform 必须写
transform: translateY(0);,不能只靠.slide-in { transform: translateY(-20px); } - max-height 可替代 height: auto,但初始值必须是具体数字:
max-height: 0;→max-height: 300px; - 用 Computed 面板确认当前值不是
auto或initial,尤其对 JS 动态插入的元素
DOM 插入与样式设置在同一个 microtask 中
动态创建元素后,如果先 appendChild() 再加 class,或 JS 同步设置样式,浏览器会把 DOM 插入、样式计算、布局合并为一次处理,跳过起始快照采集。
- ✅ 推荐顺序:
el.classList.add("fade-item"); container.appendChild(el); - ⚠️ 强制重排(如
el.offsetHeight)能补救,但别滥用 —— 它会引发 layout thrashing - ❌ 不要用
setTimeout(..., 0),在 Safari 和旧 WebView 中不可靠 - requestAnimationFrame 只解决“时机”,不补“起点缺失”;若基础类没写初始值,加了也白搭
scoped 样式或动态注入导致规则不匹配
Vue 的 <style scoped></style> 会给选择器加 data-v-xxxx 属性,JS 直接 appendChild 的节点没有该属性,CSS 规则根本不会应用。
- Vue 2 用
::v-deep(.dynamic-item),Vue 3 用:deep(.dynamic-item)穿透作用域 - 或把过渡相关样式(含基础类和
transition)抽到全局<style></style>块中 - 检查 DevTools 中动态元素是否真有对应 class,且 Computed 里的
transition不是none
最容易被忽略的是:transition 必须始终存在,不能随状态开关而消失;哪怕只是临时移除带 transition 的类一帧,动画上下文就断了,后续再加也恢复不了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











