一、Transition 单元素过渡
< Transition > 是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
1、由 v-if 所触发的切换
2、由 v-show 所触发的切换
3、由特殊元素 切换的动态组件
4、改变特殊的 key 属性
1、class 名称描述:
1. 自定义name名称-enter-from: 进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
2. 自定义name名称-enter-active: 进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
3. 自定义name名称-enter-to: 进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
4. 自定义name名称-leave-from: 离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
5. 自定义name名称-leave-active: 离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
6. 自定义name名称-leave-to: 离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。
html
<div id="app">
<button @click="isShow=!isShow">点击</button>
<transition name="mytransition" appear mode="in-out">
<div v-if="isShow">11111111</div>
</transition>
</div>
<!-- css的transition
进入动画效果为:元素在1.5s内透明度从0变为1,横坐标从100px到0px
离开效果则相反
-->
<style>
.mytransition-leave-active,
.mytransition-enter-active {
transition: all 1.5s;
}
.mytransition-leave-to,
.mytransition-enter-from {
opacity: 0;
transform: translateX(100px);
}
.mytransition-leave-from,
.mytransition-enter-to {
opacity: 1;
transform: translateX(0px);
}
<!-- css的animation,
进入动画效果为:元素在0.5s内透明度从0变为1,横坐标从100px到0px
离开则相反
-->
.mybounce-enter-active {
animation: bounce-in 0.5s;
}
.mybounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
</style>
<!-- js -->
<script>
const app = Vue.createApp({
data () {
return { isShow: true, message: 'Hello' }
}
}).mount('#app')
</script>
2、同时使用 transition 和 animation
Vue 需要附加事件监听器,以便知道过渡何时结束。可以是 transitionend 或 animationend,这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型。
然而在某些场景中,你或许想要在同一个元素上同时使用它们两个。举例来说,Vue 触发了一个 CSS 动画,同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation 或 transition:
html
<Transition type="animation">...</Transition>
3、JavaScript 钩子
可以通过监听 组件事件的方式在过渡过程中挂上钩子函数:
这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。
javascript
// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}
// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
done()
}
// 当进入过渡完成时调用。
function onAfterEnter(el) {}
// 当进入过渡在完成之前被取消时调用
function onEnterCancelled(el) {}
// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el) {}
// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
done()
}
// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el) {}
// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false" prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。
html
<Transition
...
:css="false"
>
</Transition>
在有了 :css="false" 后,我们就自己全权负责控制什么时候过渡结束了。这种情况下对于 @enter 和 @leave 钩子来说,回调函数 done 就是必须的。否则,钩子将被同步调用,过渡将立即完成。
html
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
</Transition>
4、可复用过渡效果
html
<!-- MyTransition.vue -->
<script>
// JavaScript 钩子逻辑...
</script>
<template>
<!-- 包装内置的 Transition 组件 -->
<Transition
name="my-transition"
@enter="onEnter"
@leave="onLeave">
<slot></slot> <!-- 向内传递插槽内容 -->
</Transition>
</template>
<style>
/*
必要的 CSS...
注意:避免在这里使用 <style scoped>
因为那不会应用到插槽内容上
*/
</style>
现在 MyTransition 可以在导入后像内置组件那样使用了:
html
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>
5、出现时过渡
appear :元素渲染完成后主动生效
html
<Transition appear>
...
</Transition>
6、元素间过渡
除了通过 v-if / v-show 切换一个元素,也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可:
html
<div id="app">
<button @click="changeBtn()">点击</button>
<transition name="mybounce" appear mode="out-in">
<p v-if="docState === 'saved'">Edit</p>
<p v-else-if="docState === 'edited'">Save</p>
<p v-else-if="docState === 'editing'">Cancel</p>
</transition>
</div>
<!-- css -->
<style>
.mybounce-enter-active {
animation: bounce-in 1.5s;
}
.mybounce-leave-active {
animation: bounce-in 1.5s reverse;
}
@keyframes bounce-in {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
</style>
<!-- js-->
<script>
const app = Vue.createApp({
data () {
return {
docState: 'saved'
}
},
methods: {
changeBtn () {
if (this.docState == 'saved') {
this.docState = 'edited'
} else if (this.docState === 'edited') {
this.docState = 'editing'
} else if (this.docState === 'editing') {
this.docState = 'saved'
}
}
}
}).mount('#app')
</script>
7、过渡模式
in-out :先进后出
out-in :先出后进
html
<!-- <Transition mode="in-out"> -->
<Transition mode="out-in">
...
</Transition>
8、动态组件间过渡
html
<keep-alive>
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>
</keep-alive>
9、动态过渡
html
<Transition :name="transitionName">
<!-- ... -->
</Transition>
二、TransitionGroup
< TransitionGroup > 和 < Transition > 的区别:
< TransitionGroup > 支持和 < Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
1、默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
2、过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
3、列表中的每个元素都必须有一个独一无二的 key attribute。
4、CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
<div id="app">
<input type="text" v-model="inputText" />
<button @click="addArray">add</button>
<transition-group name="mytransition" tag="ul">
<li v-for="(item,index) in inputArr" :key="item">
{{ item }} -- {{ index
}}<button @click="removeArray(index)">remove</button>
</li>
</transition-group>
</div>
<script>
const emitter = window.mitt()
const app = Vue.createApp({
data () {
return {
inputText: '',
inputArr: []
}
},
methods: {
addArray () {
let start = 0
let stop = this.inputArr.length - 1
let random = emitter.emit('getRandom', { start, stop })
this.inputArr.splice(random, start, this.inputText)
},
removeArray (index) {
this.inputArr.splice(index, 1)
}
},
mounted () {
emitter.on('getRandom', ({ start, stop }) => {
return parseInt(Math.random() * (stop - start) + start)
})
}
}).mount('#app')
</script>
<style>
/* .mytransition-move :对移动中的元素应用的过渡 */
.mytransition-move,
.mytransition-leave-active,
.mytransition-enter-active {
transition: all 1.5s;
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.mytransition-leave-active {
position: absolute;
}
.mytransition-leave-to,
.mytransition-enter-from {
opacity: 0;
transform: translateX(100px);
}
.mytransition-leave-from,
.mytransition-enter-to {
opacity: 1;
transform: translateX(0px);
}
</style>