Skip to content
本页目录

Vue Transition & TransitionGroup 组件

作用 & 触发条件

作用

在一个元素或组件进入和离开DOM时应用动画

组件的进入或离开可以由以下的条件之一触发

  • v-if 所触发的切换
  • v-show 所触发的切换
  • <component> 切换的动态组件

当一个 <Transition> 组件中的元素被插入或移除时,会发生下面这些事情:

  1. Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果存在,则一些 CSS 过渡 class 会在适当的时机被添加和移除。
  2. 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
  3. 如果没有探测到 CSS 过渡动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。

触发时机

从上面可以得知,动效的触发条件是 组件的进入或离开, 组件进入和组件离开又各自分为 3 个阶段, 开始 -> 进行中 -> 结束, vue 会在每个阶段,给<Transition> 组件中的元素,应用不同的 class

An image

Enter: 表示组件的进入

  • v-enter-from: 组件进入的开始状态
  • v-enter-active: 组件进入的进行中状态
  • v-enter-to: 组件进入的结束状态

Leave: 表示组件的离开

  • v-leave-from: 组件离开的开始状态
  • v-leave-active: 组件离开的进行中状态
  • v-leave-to: 组件离开的结束状态
上面的v可以看作是动效的默认命名, 什么意思?

就是指 vue 会在 组件进入 的开始状态给组件添加 v-enter-fromclass, 组件进入的进行中状态给组件添加v-enter-active的 class, 组件进入的结束状态给组件添加v-enter-to的 class, 组件离开也会用同样的规则给组件依次添加v-leave-from, v-leave-active, v-leave-to

如何设置动效的 class

可以通过 Transition 标签的 name 属性修改v的默认命名

js
<Transition name="fade">... </Transition>

上面的例子,则 vue 会找如下命名的 class

fade-enter-from
fade-enter-active
fade-enter-to

fade-leave-from
fade-leave-active
fade-leave-to

多 DOM 切换之元素间过渡

除了通过 v-if / v-show 切换一个元素,我们也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可:

点击切换状态,不同状态显示不同按钮
点击右侧文字切换状态 👉
<script setup>
import { ref } from 'vue'

const docState = ref('saved')
</script>

<template>
  <div class="p-8 rounded-lg linear-gradient-blue ">
    <div class="" style="position: relative;">
      <span class="text-white mr-4">点击右侧文字切换状态 👉 </span>
      <Transition name="slide-up">
        <button v-if="docState === 'saved'" @click="docState = 'edited'">
          编辑
        </button>
        <button v-else-if="docState === 'edited'" @click="docState = 'editing'">
          保存
        </button>
        <button v-else-if="docState === 'editing'" @click="docState = 'saved'">
          取消
        </button>
      </Transition>
    </div>
  </div>
</template>

<style scoped>
button {
  position: absolute;
  border: 1px solid white;
  border-radius: 3px;
  color: white !important;
  padding: 5px 15px;
  width: 100px;
}

.slide-up-enter-active,
.slide-up-leave-active {
  transition: all 0.25s ease-out;
}

.slide-up-enter-from {
  opacity: 0;
  transform: translateY(30px);
}

.slide-up-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

注意:

上面的例子,进入和离开的元素都是在同时开始动画的,因此必须将它们设为 position: absolute; 以避免二者同时存在时出现的布局问题。

多 DOM 切换之过渡模式

使用 mode属性设置过渡模式:

  • default: 进入动效和离开动效,同时执行
  • in-out: 先执行进入动效,再执行俩开动效
  • out-in: 先执行离开动效,再执行进入动效
点击切换状态,不同状态显示不同按钮,请注意分辨与上一个示例的不同之处
点击右侧文字切换状态 👉
<script setup>
import { ref } from 'vue'

const docState = ref('saved')
</script>

<template>
  <div class="p-8 rounded-lg linear-gradient-blue ">
    <div class="" style="position: relative;">
      <span class="text-white mr-4">点击右侧文字切换状态 👉 </span>
      <Transition name="slide-up">
        <button v-if="docState === 'saved'" @click="docState = 'edited'">
          编辑
        </button>
        <button v-else-if="docState === 'edited'" @click="docState = 'editing'">
          保存
        </button>
        <button v-else-if="docState === 'editing'" @click="docState = 'saved'">
          取消
        </button>
      </Transition>
    </div>
  </div>
</template>

<style scoped>
button {
  position: absolute;
  border: 1px solid white;
  border-radius: 3px;
  color: white !important;
  padding: 5px 15px;
  width: 100px;
}

.slide-up-enter-active,
.slide-up-leave-active {
  transition: all 0.25s ease-out;
}

.slide-up-enter-from {
  opacity: 0;
  transform: translateY(30px);
}

.slide-up-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

component 动态组件切换

多个Vue组件交替出现
Component A
<script lang="ts" setup>
import { defineComponent, h, ref } from 'vue'

const activeComponent = ref('CompA')
</script>

<script  lang="ts">
const CompA =  defineComponent({
  render : () => h('div', { class: 'py-10 px-20 bg-blue rounded flex_ccc' }, 'Component A'),
})
const CompB =  defineComponent({
  render : () => h('div', { class: 'py-10 px-20 bg-red rounded flex_ccc' }, 'Component B'),
})

export default {
  components: { CompA, CompB },
}
</script>

<template>
  <div class="py-8  rounded-lg linear-gradient-blue flex flex-col items-center">
    <div class="flex items-center pb-6">
      <label>
        <input v-model="activeComponent" type="radio" value="CompA"> A组件
      </label>
      <label>
        <input v-model="activeComponent" type="radio" value="CompB"> B组件
      </label>
    </div>
    <Transition name="fade" mode="out-in">
      <component :is="activeComponent" />
    </Transition>
  </div>
</template>

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

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

动画实例

淡入

仅实现了淡入动效(进入动效), 且只设置了透明度

使用 Transition标签实现:淡入


<script setup lang="ts">
import { ref } from 'vue'

const show = ref<boolean>(false)
const  onToggle = (toggle: boolean) => show.value = toggle
</script>

<template>
  <div class="p-8 rounded-lg linear-gradient-blue flex flex-col items-center min-h-40">
    <h4>使用 Transition标签实现:淡入</h4>
    <div class="flex gap-3 my-4">
      <el-button type="primary" @click="onToggle(true)">
        淡入
      </el-button>
      <el-button @click="onToggle(false)">
        隐藏
      </el-button>
    </div>
    <Transition>
      <div v-if="show" class="w-60 bg-orange-500 h-20 rounded" />
    </Transition>
  </div>
</template>

<style lang="scss" scoped>
// 动效class
// 这里可以认为是对元素进入前的样式描述
.v-enter-from {
  opacity: 0;
}
// 这里可以认为是从from样式转换到to样式的过渡方式与过渡用时的描述
.v-enter-active {
  transition: opacity 1s ease-in;
}
// 这里可以认为是对元素进入后的样式描述
.v-enter-to {
  opacity: 1;
}
</style>

淡出

仅实现了淡出动效(离开动效), 且只设置了透明度

使用Transition标签实现:淡出

<!--
淡出效果
@author: pan
@createDate: 2022-11-30 15:08
-->
<script setup lang="ts">
import { ref } from 'vue'

const show = ref<boolean>(false)
function onToggle(toggle: boolean) {
  show.value = toggle
}
</script>

<template>
  <div class="p-8 rounded-lg linear-gradient-blue flex flex-col items-center min-h-40">
    <h4>使用Transition标签实现:淡出</h4>
    <div class="flex gap-3 my-4">
      <el-button type="primary" @click="onToggle(true)">
        淡出
      </el-button>
      <el-button @click="onToggle(false)">
        显示
      </el-button>
    </div>
    <Transition>
      <div v-if="show" class="w-60  bg-purple-500 h-20 rounded" />
    </Transition>
  </div>
</template>


<style lang="scss" scoped>
.container {
  height: 146px;
}
.rect {
  width: 150px;
  height: 80px;
  background-color: lightgreen;
  margin-top: 10px;
}

// 动效class
// 这里可以认为是对元素离开前的样式描述
.v-leave-from {
  opacity: 1;
}
// 这里可以认为是从from样式转换到to样式的过渡方式与过渡用时的描述
.v-leave-active {
  transition: opacity 1s ease-in;
}
// 这里可以认为是对元素进入后的样式描述
.v-leave-to {
  opacity: 0;
}
</style>

淡入淡出

同时设置了进入动效和离开动效

使用Transition标签实现:淡入淡出

<script setup lang="ts">
import { ref } from 'vue'

const show = ref<boolean>(false)
function onToggle(toggle: boolean) {
  show.value = toggle
}
</script>

<template>
  <div class="p-8 rounded-lg linear-gradient-blue flex flex-col items-center min-h-40">
    <h4>使用Transition标签实现:淡入淡出</h4>
    <div class="flex gap-3 my-4">
      <el-button type="primary" @click="onToggle(true)">
        淡入
      </el-button>
      <el-button @click="onToggle(false)">
        淡出
      </el-button>
    </div>
    <Transition>
      <div v-if="show" class="w-60  bg-red-400 h-20 rounded" />
    </Transition>
  </div>
</template>


<style lang="scss" scoped>
// 动效class
.v-enter-from,
.v-leave-to {
  opacity: 0;
}
.v-enter-active,
.v-leave-active {
  transition: opacity 1s ease-in;
}
.v-enter-to,
.v-leave-from {
  opacity: 1;
}
</style>

TransitionGroup 标签

作用和特点

作用:用于给一组元素添加动效

  • 默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
  • 过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换
  • 列表中的每个元素都必须有一个独一无二的 key attribute
  • CSS过渡 class 会被应用在列表内的元素上,而不是容器元素上。

当在 DOM 模板中使用时,组件名需要写为 <transition-group>

这里为了保证移除元素的丝滑,增加了一个.fade-move的过渡
    1
    2
    3
    4
    5
<!--
通过内建的 <TransitionGroup> 实现“FLIP”列表过渡效果。
https://aerotwist.com/blog/flip-your-animations/
-->

<script setup lang="ts">
import { shuffle } from 'lodash-es'
import { ref } from 'vue'

const getInitialItems = () => [1, 2, 3, 4, 5]
let id = getInitialItems().length + 1

const items = ref(getInitialItems())

const insert = () => {
  const i = Math.round(Math.random() * items.value.length)
  items.value.splice(i, 0, id++)
}

const  reset = () => items.value = getInitialItems()

const shuffleFn = () =>  {
  items.value = shuffle(items.value)
}

const  remove = (item)  => {
  const i =  items.value.indexOf(item)
  if (i > -1)
    items.value.splice(i, 1)
}
</script>

<template>
  <div class="px-5 py-3">
    <el-button class="mr-3" @click="insert">
      随机一个位置插入一个元素
    </el-button>
    <el-button class="mr-3" @click="reset">
      重置
    </el-button>
    <el-button class="mr-3" @click="shuffleFn">
      随机变化位置
    </el-button>
  </div>
  <TransitionGroup tag="ul" name="fade" class="relative p-0 text-blue-gray-900">
    <div v-for="item in items" :key="item" class="w-full py-2 px-4 bg-warm-gray-100 border border-red-300 border-solid flex items-center">
      <div class="min-w-4 ">
        {{ item }}
      </div>
      <button class="flex_ccc  w-6 h-6 border rounded-full hover:bg-purple-300 border-purple-500 border-solid ml-7" @click="remove(item)">
        x
      </button>
    </div>
  </TransitionGroup>
</template>

<style scoped>
/* 1. 声明过渡效果 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. 声明进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: scaleY(0.01) translate(30px, 0);
}

/* 3. 确保离开的项目被移除出了布局流
  以便正确地计算移动时的动画效果。 */
.fade-leave-active {
  position: absolute;
}
</style>