第二章:单元素/组件的过渡——<transition>组件基础、v-if/v-show过渡、动态切换过渡效果

好,咱们进入正题。单元素或组件的过渡,是 Vue 动画体系里最基础、也最常用的一块。说白了,就是控制一个元素「怎么出现」和「怎么消失」。你想想看,页面上的弹窗、提示、切换标签,哪个不需要这种效果?

我个人习惯把这一章当作 Vue 动画的「敲门砖」。搞懂了它,后面那些复杂的列表动画、状态动画,理解起来就顺多了。

2.1 <transition> 组件:Vue 动画的基石

Vue 提供了一个内置组件叫 <transition>。它不渲染任何 DOM 元素,只负责在元素插入或移除时,自动添加/移除 CSS 类名。你只需要写好 CSS,动画就出来了。

我在项目中遇到过不少新手,直接给元素写 transition: all 0.3s,然后发现用 v-if 控制显示隐藏时,动画根本不生效。为什么?因为 v-if 是直接销毁和重建 DOM,浏览器没有机会执行过渡。而 <transition> 就是来解决这个问题的。

基本用法长这样:

<template>
  <button @click="show = !show">切换</button>
  <transition name="fade">
    <p v-if="show">Hello Vue Animation</p>
  </transition>
</template>

<script>
export default {
  data() {
    return { show: true }
  }
}
</script>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

这里的关键是 name="fade"。Vue 会自动生成六个 CSS 类名:

阶段 类名 说明
进入开始 fade-enter-from 元素插入前的初始状态
进入激活 fade-enter-active 整个进入过程的过渡效果
进入结束 fade-enter-to 元素插入后的最终状态
离开开始 fade-leave-from 离开前的初始状态
离开激活 fade-leave-active 整个离开过程的过渡效果
离开结束 fade-leave-to 离开后的最终状态
小技巧:如果你不设置 name 属性,默认类名前缀是 v-。比如 v-enter-from。我个人建议总是显式设置 name,避免多个过渡组件冲突。

2.2 v-ifv-show 的过渡差异

嗯,这里要注意一个常见的坑。很多人以为 v-ifv-show<transition> 包起来效果一样。其实不然。

  • v-if:元素会被真正销毁和重建。过渡效果完整,进入和离开动画都会执行。
  • v-show:元素始终存在,只是切换 display 属性。离开动画会执行,但进入动画可能「一闪而过」。

为什么会这样?因为 v-show 在切换时,元素并没有从 DOM 中移除。Vue 的 <transition> 机制依赖于元素的插入和移除事件。对于 v-show,它只能触发 display 的变化,而 display: nonedisplay: block 的切换,浏览器不会自动触发 CSS 过渡。

我曾经在一个后台管理系统里,用 v-show 控制侧边栏的展开收起,结果发现展开时动画总是「卡一下」。排查了半天,才发现是 v-show 的问题。后来改成 v-if 配合 <transition>,效果就流畅了。

如果你确实需要用 v-show 做过渡,可以这样处理:

<transition name="slide">
  <div v-show="visible" class="panel">
    内容区域
  </div>
</transition>

<style>
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}
.slide-enter-from,
.slide-leave-to {
  opacity: 0;
  transform: translateX(-20px);
}
/* 关键:强制初始状态 */
.panel {
  display: block !important;
}
</style>
避坑指南:v-show 做过渡时,记得给元素加上 display: block !important,否则 Vue 的过渡类名可能被 v-show 的内联样式覆盖。我曾经因为这个 bug 被测试小姐姐追着问了一下午...

2.3 动态切换过渡效果

有时候,我们需要根据不同的场景使用不同的过渡效果。比如:从左侧滑入用 slide-left,从右侧滑入用 slide-right。Vue 的 <transition> 组件支持通过 name 属性动态绑定。

<template>
  <button @click="direction = 'left'">从左进入</button>
  <button @click="direction = 'right'">从右进入</button>
  
  <transition :name="transitionName">
    <p v-if="show" key="content">
      当前过渡方向:{{ direction }}
    </p>
  </transition>
</template>

<script>
export default {
  data() {
    return {
      show: true,
      direction: 'left'
    }
  },
  computed: {
    transitionName() {
      return this.direction === 'left' ? 'slide-left' : 'slide-right'
    }
  }
}
</script>

<style>
.slide-left-enter-active,
.slide-left-leave-active {
  transition: all 0.3s ease;
}
.slide-left-enter-from {
  transform: translateX(-100%);
  opacity: 0;
}
.slide-left-leave-to {
  transform: translateX(100%);
  opacity: 0;
}

.slide-right-enter-active,
.slide-right-leave-active {
  transition: all 0.3s ease;
}
.slide-right-enter-from {
  transform: translateX(100%);
  opacity: 0;
}
.slide-right-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}
</style>

这里有个细节:我加了一个 key="content"。为什么?因为 Vue 在复用相同标签元素时,会尽量复用 DOM 节点。如果不加 key,切换方向时可能不会触发重新渲染,过渡效果就出不来了。你想想看,Vue 觉得「嗯,还是那个 p 标签,不用动」,那动画自然就没了。

动态切换过渡效果在实际项目中非常实用。我记得做过一个多步骤表单,每一步的切换方向不同——下一步从左滑入,上一步从右滑入。用动态 name 配合 key,几行代码就搞定了。

核心要点:
  • <transition> 只对单个元素/组件生效
  • 必须配合 v-ifv-show 或动态组件使用
  • 动态 name 可以实现不同场景的过渡切换
  • 相同标签元素记得加 key,否则动画可能不触发

好了,这一章的内容就这些。说白了就是三件事:用 <transition> 包住元素、写好 CSS 类名、根据场景动态切换。下一章我们会聊多元素过渡和列表过渡,到时候你会发现,基础打牢了,复杂的东西其实也没那么难。