目录
1.单元素/组件的过渡
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件 is属性
- 组件根节点
简单案例
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: {
show: true
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
- 如果过渡组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。
2.过渡的类名
在进入/离开的过渡中,会有 6 个 class 切换。
- v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
- v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
- v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
- v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
- v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
- v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
初始渲染的过渡
可以通过 appear attribute 设置节点在初始渲染的过渡
<transition appear>
<!-- ... -->
</transition>
这里默认和进入/离开过渡一样,同样也可以自定义 CSS 类名。
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class"
appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>
多个元素的过渡
我们之后讨论多个组件的过渡,对于原生标签可以使用 v-if/v-else。最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:
<transition>
<div v-if="items.length > 0">
<!-- ... -->
</div>
<p v-else>Sorry, no items found.</p>
</transition>
可以这样使用,但是有一点需要注意:
当有相同标签名的元素切换时,需要通过 key attribute 设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践。
如下:
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
过渡模式
同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式
- in-out:新元素先进行过渡,完成之后当前元素过渡离开。
- out-in:当前元素先进行过渡,完成之后新元素过渡进入。
如果没有这个过渡模式,传统实现方案,采用定位也可以解决。
只用添加一个简单的 attribute,就解决了之前的过渡问题而无需任何额外的代码。
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
3.多个组件的过渡
多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件:
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
4.列表过渡
目前为止,关于过渡我们已经讲到:
- 单个节点
- 同一时间渲染多个节点中的一个
那么怎么同时渲染整个列表,比如使用 v-for?在这种场景中,使用 <transition-group> 组件。
在我们深入例子之前,先了解关于这个组件的几个特点:
- 不同于 <transition>,它会以一个真实元素呈现:默认为一个 <span>。你也可以通过 tag attribute 更换为其他元素。
- 过渡模式mode属性不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的 key attribute 值,并且避免使用index索引
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。 ul 和li,动画产生在li,不会在ul上产生动画
列表的进入/离开过渡
现在让我们由一个简单的例子深入,进入和离开的过渡使用之前一样的 CSS 类名。
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
new Vue({
el: '#list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
5.可复用的过渡
过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,你需要做的就是将 <transition> 或者 <transition-group> 作为根组件,然后将任何子组件放置在其中就可以了。
使用 template 的简单例子:
Vue.component('my-special-transition', {
template: '\
<transition\
name="very-special-transition"\
mode="out-in"\
>\
<slot></slot>\
</transition>\
',
methods: {
}
})
抽屉案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>Document</title>
<script src="./vue.js"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<style>
*{margin:0;padding:0;}
body,html{
height:100%;
}
.fade-enter-active{
animation: aa 1s;
}
.fade-leave-active{
animation: aa 1s reverse;
}
@keyframes aa{
0%{
opacity:0;
}
100%{
opacity:1;
}
}
.left{
position:fixed;
top:0;
left:0;
background-color:red;
}
.left-enter-active{
animation: leftframe 1s;
}
.left-leave-active{
animation: leftframe 1s reverse;
}
@keyframes leftframe {
0%{
/* opacity:0; */
transform: translateX(-100%);
}
100%{
/* opacity:1; */
transform: translateX(0px);
}
}
.right{
position:fixed;
top:0;
right:0;
background-color:red;
}
.right-enter-active{
animation: rightframe .3s;
}
.right-leave-active{
animation: rightframe .3s reverse;
}
@keyframes rightframe {
0%{
/* opacity:0; */
transform: translateX(100%);
}
100%{
/* opacity:1; */
transform: translateX(0px);
}
}
</style>
</head>
<body>
<!-- https://animate.style/ -->
<!-- animate__animated 第一个:必须要有的 不要动-->
<!-- animate__bounce 第二个:代表执行哪个动画-->
<!-- <h1 class="animate__animated animate__bounceOutLeft">An animated element</h1> -->
<div id="app">
<button @click="isShow = !isShow">切换</button>
<transition name="fade">
<div v-if="isShow">内容1------</div>
</transition>
<div>---------------------------</div>
<fade>
<div v-if="isShow">内容2------</div>
</fade>
<!-- 组件完成模块或功能的封装,实现可复用 -->
<!-- 插槽在组件的基础上完成内容的分发 -->
<div>----------------------------</div>
<div>
<button @click="show('left')">left显示</button>
<button @click="hide()">隐藏</button>
</div>
<div>
<button @click="show('right')">right显示</button>
<button @click="hide()">隐藏</button>
</div>
<!-- v-if v-show -->
<drawer :placement="pos" :show="sliderShow"></drawer>
</div>
<script>
Vue.component('fade',{
template:`
<transition name="fade">
<slot></slot>
</transition>
`
});
Vue.component('drawer',{
props:{
title:{
default:'标题'
},
placement:{
default:'left'
},
show:{
default:false
}
},
template:`
<transition :name="placement">
<div :class="placement" v-if="show">
<h5 v-text="title"></h5>
<div>
content
</div>
</div>
</transition>
`
});
var vm = new Vue({
el:'#app',
data:{
isShow:true,
sliderShow:false,
pos:'left'
},
methods:{
show(pos){
this.pos = pos;
this.sliderShow = true;
},
hide(){
this.sliderShow = false;
}
}
});
</script>
</body>
</html>