Vue 3 过渡动画基础类名

1. 过渡动画概述

1.1 什么是过渡动画

过渡动画是指元素在进入或离开DOM时,通过CSS类名的切换实现的平滑过渡效果。Vue提供了内置的<transition>组件,用于处理元素的进入和离开动画。

1.2 过渡动画的应用场景

  • 元素的显示/隐藏
  • 组件的切换
  • 路由的跳转
  • 列表的增删改查

1.3 Vue过渡系统的特点

  • 基于CSS类名的切换
  • 支持CSS过渡和CSS动画
  • 支持JavaScript钩子函数
  • 支持自定义过渡类名
  • 支持嵌套过渡

2. 过渡动画的基础类名

Vue的过渡系统会在元素进入或离开DOM时,自动添加和移除一系列CSS类名。这些类名遵循特定的命名规则,用于控制过渡的各个阶段。

2.1 基本过渡类名

当使用<transition name="fade">时,Vue会自动生成以下类名:

类名 描述 应用时机
fade-enter-from 进入过渡的起始状态 元素被插入前添加,插入后移除
fade-enter-active 进入过渡的激活状态 整个进入过渡期间应用,插入前添加,过渡完成后移除
fade-enter-to 进入过渡的结束状态 元素被插入后添加,过渡完成后移除
fade-leave-from 离开过渡的起始状态 离开过渡开始时添加,离开过渡触发后立即移除
fade-leave-active 离开过渡的激活状态 整个离开过渡期间应用,离开过渡开始时添加,过渡完成后移除
fade-leave-to 离开过渡的结束状态 离开过渡开始后添加,过渡完成后移除

2.2 过渡类名的生命周期

进入过渡:

  1. 元素被插入DOM前:添加fade-enter-fromfade-enter-active
  2. 元素被插入DOM后:移除fade-enter-from类,添加fade-enter-to
  3. 过渡结束后:移除fade-enter-activefade-enter-to

离开过渡:

  1. 离开过渡触发时:添加fade-leave-fromfade-leave-active
  2. 下一帧:移除fade-leave-from类,添加fade-leave-to
  3. 过渡结束后:移除fade-leave-activefade-leave-to类,同时移除元素

2.3 过渡类名的优先级

Vue的过渡类名具有较高的优先级,会覆盖普通的CSS类名。这是因为Vue在编译时会为过渡类名添加组件实例的唯一属性选择器,例如:

.fade-enter-active[data-v-7ba5bd90] {
  /* 过渡样式 */
}

3. 基本使用示例

3.1 CSS过渡效果

<template>
  <div>
    <button @click="show = !show">Toggle</button>
    <transition name="fade">
      <div v-if="show" class="box">
        Hello Transition
      </div>
    </transition>
  </div>
</template>

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

<style>
.box {
  width: 200px;
  height: 200px;
  background-color: #42b983;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}

/* 进入过渡 */
.fade-enter-from {
  opacity: 0;
  transform: scale(0.5);
}

.fade-enter-active {
  transition: all 0.3s ease;
}

.fade-enter-to {
  opacity: 1;
  transform: scale(1);
}

/* 离开过渡 */
.fade-leave-from {
  opacity: 1;
  transform: scale(1);
}

.fade-leave-active {
  transition: all 0.3s ease;
}

.fade-leave-to {
  opacity: 0;
  transform: scale(0.5);
}
</style>

3.2 CSS动画效果

CSS动画与CSS过渡类似,但动画是通过@keyframes定义的:

<template>
  <div>
    <button @click="show = !show">Toggle Animation</button>
    <transition name="bounce">
      <div v-if="show" class="box">
        Hello Animation
      </div>
    </transition>
  </div>
</template>

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

<style>
.box {
  width: 200px;
  height: 200px;
  background-color: #3498db;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}

/* 进入动画 */
.bounce-enter-active {
  animation: bounce-in 0.5s;
}

/* 离开动画 */
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}

/* 定义动画 */
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}
</style>

4. 自定义过渡类名

Vue允许我们通过自定义过渡类名,使用第三方CSS动画库(如Animate.css):

4.1 使用自定义类名

<template>
  <div>
    <button @click="show = !show">Toggle Animate.css</button>
    <transition
      enter-active-class="animate__animated animate__fadeIn"
      leave-active-class="animate__animated animate__fadeOut"
    >
      <div v-if="show" class="box">
        Hello Animate.css
      </div>
    </transition>
  </div>
</template>

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

<style>
/* 引入Animate.css */
@import 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css';

.box {
  width: 200px;
  height: 200px;
  background-color: #e74c3c;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}
</style>

4.2 所有自定义类名

属性名 描述
enter-from-class 自定义进入起始类名
enter-active-class 自定义进入激活类名
enter-to-class 自定义进入结束类名
leave-from-class 自定义离开起始类名
leave-active-class 自定义离开激活类名
leave-to-class 自定义离开结束类名

5. 过渡动画的配置选项

5.1 持续时间

可以通过duration属性设置过渡的持续时间:

<template>
  <div>
    <transition name="fade" :duration="1000">
      <div v-if="show" class="box">
        Slow Transition
      </div>
    </transition>
    
    <!-- 分别设置进入和离开的持续时间 -->
    <transition name="fade" :duration="{ enter: 500, leave: 800 }">
      <div v-if="show" class="box">
        Different Durations
      </div>
    </transition>
  </div>
</template>

5.2 过渡模式

默认情况下,进入和离开动画会同时进行。可以通过mode属性设置过渡模式:

  • **in-out**:新元素先进入,旧元素再离开
  • **out-in**:旧元素先离开,新元素再进入
<template>
  <div>
    <button @click="view = view === 'A' ? 'B' : 'A'">
      Toggle View
    </button>
    
    <transition name="fade" mode="out-in">
      <component :is="view" key="view"></component>
    </transition>
  </div>
</template>

<script>
const ComponentA = { template: '<div class="box">Component A</div>' }
const ComponentB = { template: '<div class="box">Component B</div>' }

export default {
  components: {
    ComponentA,
    ComponentB
  },
  data() {
    return {
      view: 'A'
    }
  }
}
</script>

5.3 动画结束的检测

Vue会自动检测过渡或动画的结束,但在某些情况下(如使用JavaScript动画库),可能需要手动触发结束事件:

<template>
  <div>
    <transition
      name="custom"
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
      :css="false"
    >
      <div v-if="show" class="box">
        JavaScript Animation
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = 0
      el.style.transform = 'translateX(-100px)'
    },
    enter(el, done) {
      // 使用GSAP等动画库
      // gsap.to(el, { opacity: 1, x: 0, duration: 0.5, onComplete: done })
      
      // 手动实现动画
      let opacity = 0
      let x = -100
      const step = () => {
        opacity += 0.05
        x += 5
        el.style.opacity = opacity
        el.style.transform = `translateX(${x}px)`
        
        if (opacity < 1) {
          requestAnimationFrame(step)
        } else {
          done() // 必须调用done()通知Vue动画结束
        }
      }
      requestAnimationFrame(step)
    },
    leave(el, done) {
      // 离开动画逻辑
      done()
    }
  }
}
</script>

6. 不同类型的过渡

6.1 单个元素的过渡

最基本的过渡用法,用于单个元素的显示和隐藏:

<transition name="fade">
  <div v-if="show" class="box">Single Element</div>
</transition>

6.2 动态组件的过渡

用于动态组件的切换:

<transition name="fade" mode="out-in">
  <component :is="currentComponent" key="currentComponent"></component>
</transition>

6.3 条件渲染的过渡

用于v-if/v-else/v-else-if的条件渲染:

<transition name="fade">
  <div v-if="condition" class="box">A</div>
  <div v-else class="box">B</div>
</transition>

6.4 事件驱动的过渡

用于事件触发的过渡效果:

<transition name="shake" appear>
  <div class="box" @click="shake = !shake" :key="shake">
    Click to Shake
  </div>
</transition>

7. 过渡动画的最佳实践

7.1 性能优化

  • 使用CSS过渡和动画:优先使用CSS过渡和动画,避免使用JavaScript动画
  • 使用transform和opacity:这两个属性的过渡不会触发重排,性能更好
  • 避免使用display属性:display属性的切换会导致过渡效果失效
  • 使用will-change:对于复杂的动画,可以使用will-change属性提示浏览器优化

7.2 动画设计原则

  • 简洁明了:动画效果应简洁,避免过度使用
  • 有意义:动画应增强用户体验,而不是分散注意力
  • 一致的风格:整个应用的动画风格应保持一致
  • 适当的持续时间:动画持续时间应适中,一般在200-500ms之间

7.3 可访问性考虑

  • 提供关闭动画的选项:为用户提供关闭动画的选项,尤其是对于有前庭障碍的用户
  • 使用prefers-reduced-motion媒体查询:检测用户是否偏好减少动画
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

7.4 调试技巧

  • 使用浏览器开发者工具:观察类名的添加和移除
  • 添加过渡延迟:便于观察过渡的各个阶段
  • 使用appear属性:使元素初始渲染时也应用过渡效果
  • 检查CSS选择器:确保过渡类名的选择器优先级正确

8. 常见问题与解决方案

8.1 过渡效果不生效

问题:元素显示/隐藏时没有过渡效果

解决方案

  • 检查是否包裹了&lt;transition&gt;组件
  • 检查name属性是否正确
  • 检查CSS类名是否与name属性匹配
  • 检查元素是否有唯一的key属性
  • 检查是否使用了v-if/v-show指令

8.2 过渡效果闪烁

问题:元素在过渡过程中出现闪烁

解决方案

  • 确保enter-from和leave-to状态设置了正确的初始值
  • 避免使用display属性,可以使用visibility属性替代
  • 添加backface-visibility: hidden属性

8.3 多个元素同时过渡

问题:多个元素的过渡效果相互影响

解决方案

  • 为每个元素添加唯一的key属性
  • 使用mode属性控制过渡顺序
  • 为不同元素使用不同的过渡名称

8.4 过渡结束后样式残留

问题:过渡结束后,某些样式仍然残留

解决方案

  • 确保过渡类名的样式只在过渡期间生效
  • 使用!important覆盖默认样式
  • 检查是否有其他CSS规则影响过渡效果

9. 综合示例

9.1 卡片翻转效果

<template>
  <div class="card-container">
    <transition name="flip" mode="out-in">
      <div 
        class="card" 
        :class="{ flipped: isFlipped }"
        @click="isFlipped = !isFlipped"
        key="isFlipped"
      >
        <div class="card-front">
          <h2>Card Front</h2>
          <p>Click to flip</p>
        </div>
        <div class="card-back">
          <h2>Card Back</h2>
          <p>Hello from the other side!</p>
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isFlipped: false
    }
  }
}
</script>

<style>
.card-container {
  perspective: 1000px;
  width: 300px;
  height: 200px;
  margin: 50px auto;
}

.card {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.6s;
}

.card-front,
.card-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 18px;
}

.card-front {
  background-color: #42b983;
}

.card-back {
  background-color: #3498db;
  transform: rotateY(180deg);
}

/* 翻转过渡 */
.flip-enter-from {
  transform: rotateY(180deg);
}

.flip-enter-active {
  transition: transform 0.6s;
}

.flip-enter-to {
  transform: rotateY(0deg);
}

.flip-leave-from {
  transform: rotateY(0deg);
}

.flip-leave-active {
  transition: transform 0.6s;
}

.flip-leave-to {
  transform: rotateY(180deg);
}
</style>

9.2 模态框过渡效果

<template>
  <div>
    <button @click="showModal = true">Open Modal</button>
    
    <transition name="modal">
      <div v-if="showModal" class="modal-overlay" @click="showModal = false">
        <transition name="modal-content">
          <div class="modal-content" @click.stop>
            <h2>Modal Title</h2>
            <p>This is a modal dialog with transition effects.</p>
            <button @click="showModal = false">Close</button>
          </div>
        </transition>
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showModal: false
    }
  }
}
</script>

<style>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-content {
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  max-width: 500px;
  width: 100%;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}

/* 遮罩层过渡 */
.modal-enter-from {
  opacity: 0;
}

.modal-enter-active {
  transition: opacity 0.3s ease;
}

.modal-enter-to {
  opacity: 1;
}

.modal-leave-from {
  opacity: 1;
}

.modal-leave-active {
  transition: opacity 0.3s ease;
}

.modal-leave-to {
  opacity: 0;
}

/* 内容过渡 */
.modal-content-enter-from {
  opacity: 0;
  transform: scale(0.8) translateY(-50px);
}

.modal-content-enter-active {
  transition: all 0.3s ease;
}

.modal-content-enter-to {
  opacity: 1;
  transform: scale(1) translateY(0);
}

.modal-content-leave-from {
  opacity: 1;
  transform: scale(1) translateY(0);
}

.modal-content-leave-active {
  transition: all 0.3s ease;
}

.modal-content-leave-to {
  opacity: 0;
  transform: scale(0.8) translateY(-50px);
}
</style>

10. 总结

Vue的过渡系统基于CSS类名的切换,提供了强大的过渡动画能力。通过理解过渡动画的基础类名和生命周期,我们可以创建各种平滑的过渡效果。

主要知识点:

  1. 过渡动画的基础类名:enter-from、enter-active、enter-to、leave-from、leave-active、leave-to
  2. 过渡动画的生命周期:进入过渡和离开过渡的各个阶段
  3. 自定义过渡类名:支持使用第三方CSS动画库
  4. 过渡动画的配置选项:duration、mode、css等
  5. 不同类型的过渡:单个元素、动态组件、条件渲染等
  6. 过渡动画的最佳实践:性能优化、可访问性考虑等

过渡动画是提升用户体验的重要手段,合理使用过渡动画可以使应用更加生动和友好。但也要注意不要过度使用,避免影响应用的性能和可用性。

11. 练习

  1. 使用基本过渡类名创建一个淡入淡出效果
  2. 使用CSS动画创建一个弹跳效果
  3. 结合Animate.css创建复杂的过渡效果
  4. 实现一个卡片翻转的3D过渡效果
  5. 实现一个模态框的过渡效果
  6. 为路由切换添加过渡动画

12. 进一步阅读

« 上一篇 CSS-in-JS方案探索 下一篇 » JavaScript动画钩子