Vue Transition & TransitionGroup 组件
作用 & 触发条件
作用
在一个元素或组件进入和离开DOM
时应用动画
组件的进入或离开可以由以下的条件之一触发
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由
<component>
切换的动态组件
当一个 <Transition>
组件中的元素被插入或移除时,会发生下面这些事情:
- Vue 会自动检测目标元素是否应用了
CSS
过渡或动画。如果存在,则一些CSS
过渡class
会在适当的时机被添加和移除。 - 如果有作为监听器的
JavaScript
钩子,这些钩子函数会在适当时机被调用。 - 如果没有探测到
CSS
过渡
或动画
、也没有提供JavaScript
钩子,那么DOM
的插入、删除操作将在浏览器的下一个动画帧后执行。
触发时机
从上面可以得知,动效的触发条件是 组件的进入或离开
, 组件进入和组件离开又各自分为 3 个阶段, 开始 -> 进行中 -> 结束
, vue 会在每个阶段,给<Transition>
组件中的元素,应用不同的 class
Enter
: 表示组件的进入
- v-enter-from: 组件进入的开始状态
- v-enter-active: 组件进入的进行中状态
- v-enter-to: 组件进入的结束状态
Leave
: 表示组件的离开
- v-leave-from: 组件离开的开始状态
- v-leave-active: 组件离开的进行中状态
- v-leave-to: 组件离开的结束状态
上面的v
可以看作是动效的默认命名, 什么意思?
就是指 vue
会在 组件进入
的开始状态给组件添加 v-enter-from
的 class
, 组件进入的进行中状态给组件添加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 动态组件切换
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>
。
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>