<TransitionGroup>
是一个内置组件,用于对 v-for
列表中的元素或组件的插入、移除和顺序改变添加动画效果。
和 <Transition>
的区别
<TransitionGroup>
支持和 <Transition>
基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
-
默认情况下,它不会渲染一个容器元素。但你可以通过传入
tag
prop 来指定一个元素作为容器元素来渲染。 -
过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
-
列表中的每个元素都必须有一个独一无二的
key
attribute。 -
CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
<!--
通过内建的 <TransitionGroup> 实现“FLIP”列表过渡效果。
https://aerotwist.com/blog/flip-your-animations/
-->
<script setup>
import { shuffle as _shuffle } from 'lodash-es'
import { ref } from 'vue'
const getInitialItems = () => [1, 2, 3, 4, 5]
const items = ref(getInitialItems())
let id = items.value.length + 1
function insert() {
const i = Math.round(Math.random() * items.value.length)
items.value.splice(i, 0, id++)
}
function reset() {
items.value = getInitialItems()
id = items.value.length + 1
}
function shuffle() {
items.value = _shuffle(items.value)
}
function remove(item) {
const i = items.value.indexOf(item)
if (i > -1) {
items.value.splice(i, 1)
}
}
</script>
<template>
<button @click="insert">insert at random index</button>
<button @click="reset">reset</button>
<button @click="shuffle">shuffle</button>
<TransitionGroup tag="ul" name="fade" class="container">
<div v-for="item in items" class="item" :key="item">
{{ item }}
<button @click="remove(item)">x</button>
</div>
</TransitionGroup>
</template>
<style>
.container {
position: relative;
padding: 0;
}
.item {
width: 100%;
height: 30px;
background-color: #f3f3f3;
border: 1px solid #666;
box-sizing: border-box;
}
/* 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>
渐进延迟列表动画
通过在 JavaScript 钩子中读取元素的 data attribute,我们可以实现带渐进延迟的列表动画。首先,我们把每一个元素的索引渲染为该元素上的一个 data attribute:
接着,在 JavaScript 钩子中,我们基于当前元素的 data attribute 对该元素的进场动画添加一个延迟。
<script setup>
import { ref, computed } from 'vue'
import gsap from 'gsap'
const list = [
{ msg: 'Bruce Lee' },
{ msg: 'Jackie Chan' },
{ msg: 'Chuck Norris' },
{ msg: 'Jet Li' },
{ msg: 'Kung Fury' }
]
const query = ref('')
const computedList = computed(() => {
return list.filter((item) => item.msg.toLowerCase().includes(query.value))
})
function onBeforeEnter(el) {
el.style.opacity = 0
el.style.height = 0
}
function onEnter(el, done) {
gsap.to(el, {
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15,
onComplete: done
})
}
function onLeave(el, done) {
gsap.to(el, {
opacity: 0,
height: 0,
delay: el.dataset.index * 0.15,
onComplete: done
})
}
</script>
<template>
<input v-model="query" />
<TransitionGroup
tag="ul"
:css="false"
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
>
<li
v-for="(item, index) in computedList"
:key="item.msg"
:data-index="index"
>
{{ item.msg }}
</li>
</TransitionGroup>
</template>