transition 必须写在初始状态,不能只写在 hover 或 v-if 切换后;需在初始样式中声明 transition 属性,否则首次变化无动画;scoped 下需用 /deep/ 或 ::v-deep 穿透;transition 子节点必须唯一;必须配合 v-if 而非 v-show 使用。

transition 必须写在初始状态,不能只写在 hover 或 v-if 切换后
Vue 的 <transition></transition> 本身不控制 CSS 动画逻辑,它只是按阶段给元素加类名(如 v-enter-active、fade-leave-to)。这些类名要生效,对应 CSS 规则里的 transition 属性必须已存在于元素的「初始渲染样式」中。
常见错误是把 transition: opacity 0.3s; 只写在 .fade-enter-active 里,而没写在默认状态(比如 .fade-enter 或基础类)上。结果就是:元素刚插入时没有过渡定义,浏览器直接跳到目标值。
- ✅ 正确做法:在初始状态(哪怕只是空规则)中声明
transition,例如:.fade-enter, .fade-leave-to { opacity: 0; }<br>.fade-enter-active, .fade-leave-active {<br> transition: opacity 0.3s ease;<br>} - ❌ 错误写法:
.fade-enter-active { transition: opacity 0.3s; }单独存在,但.fade-enter没定义任何样式 → 浏览器认为该元素初始无transition,首次变化不触发动画 - 注意:Vue2 默认类名是
v-enter/v-leave,2.1.8+ 推荐用v-enter-from/v-leave-from,但无论哪套,-active类只负责「过程」,不是「起点」
scoped 样式下 .fade-* 类名匹配失败
在 Vue2 的 <style scoped></style> 中,所有类名会被自动加上 data-v-xxxx 属性选择器。但 <transition></transition> 添加的 fade-enter-active 等类名是运行时动态加上的,不带这个哈希 —— 导致样式完全不命中。
这不是 Vue Bug,而是 scoped 机制的必然行为。不解决就永远看不到动画。
- ✅ 解决方案:用深度选择器穿透 scoped 限制,推荐写法:
/deep/ .fade-enter-active,<br>/deep/ .fade-leave-active {<br> transition: opacity 0.3s;<br>} - ⚠️ 注意:
/deep/在某些构建配置中可能被弃用,可改用::v-deep .fade-enter-active(需确认 loader 支持) - 别用
<style></style>不加 scoped —— 容易污染全局;也别把动画样式提到父组件,破坏封装性
transition 包裹的不是单个根元素
Vue 的 <transition></transition> 要求其**直接子节点必须是且仅是一个 DOM 元素或一个返回单个根节点的组件**。如果子节点是多个标签、文本节点、注释,或组件内部返回了 Fragment(Vue2 不支持多根),就会报错或静默失效。
典型报错如 Vue3 的 "non-element root node",但在 Vue2 中往往表现为动画不触发、类名不添加、甚至控制台无提示。
- ✅ 检查方式:打开开发者工具 Elements 面板,确认
<transition></transition>下是否只有一个顶层元素(如<div>)<li>❌ 常见错误写法:<pre class="brush:php;toolbar:false;"><transition name="fade"><br><h2>标题</h2> <br><p>内容</p> <br></transition></pre> → 两个兄弟节点,无效</li> <li>✅ 修复:用一个容器包裹,例如:<pre class="brush:php;toolbar:false;"><transition name="fade"><br><div> <br><h2>标题</h2> <br><p>内容</p> <br> </div> <br></transition></pre> </li> <h3>用了 v-show 而不是 v-if</h3> <p><code>v-show是通过切换display: none控制显隐,DOM 始终存在;而<transition></transition>的 enter/leave 生命周期只在元素**插入或移除 DOM** 时触发 —— 所以v-show下,enter和leave阶段根本不会运行。你看到的“没动画”,其实是根本没有进入 transition 流程。
- ✅ 必须用
v-if(或v-else)配合<transition></transition>,例如:<transition name="fade" mode="out-in"><br><div v-if="tab === 'info'">信息页</div> <br><div v-else>详情页</div> <br></transition>
- ⚠️
mode="out-in"很关键:避免两个 tab 同时存在导致布局抖动 - 别试图给
v-show元素加transition类名 —— 它不走 Vue 过渡系统,只能靠纯 CSS + JS 监听 display 变化,得不偿失
最容易被忽略的是:transition 生效与否,和“有没有写”关系不大,而取决于「类名是否匹配」「DOM 是否真正进出」「初始样式是否存在 transition 声明」这三件事是否同时满足。尤其在 scoped 场景下,漏掉
/deep/几乎是必现问题,但控制台又不报错,排查起来最耗时间。 - ✅ 必须用











