vue过渡和动画
过渡:从一种状态过渡到另外一种状态,只有两种状态,一般只需要我们去做两个关键帧,其他的关键帧是由计算机帮我们生成的
动画:多个关键帧,多段,多状态,我们可以去定义自己的关键帧
我们先来回顾一下css3的过渡和动画
动画:
<style>
@keyframes move {
0% {
transform: translateX(100px);
}
50% {
transform: translateX(50px);
}
100% {
transform: translateX(0px);
}
}
.animation {
animation: move 3s;
}
</style>
<body>
<div id='app'>
<div class="animation">
<h1>hello world</h1>
</div>
<div :class="animate">
<h1>hello world</h1>
</div>
<button @click='fn'>按钮</button>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
animate: {
animation: true
}
},
methods: {
fn() {
this.animate.animation = !this.animate.animation
}
}
})
</script>
</body>
过渡:
<style>
.bgc_pink {
background-color: pink;
}
.bgc_blue {
background-color: skyblue;
}
.transition {
transition: 2s linear;
}
</style>
<body>
<div id='app'>
<div :class='animate'>
<h1>are you ok?</h1>
</div>
<button @click='fn'>按钮</button>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
animate: {
transition: true,
bgc_pink: true,
bgc_blue: false,
}
},
methods: {
fn() {
this.animate.bgc_pink = !this.animate.bgc_pink,
this.animate.bgc_blue = !this.animate.bgc_blue
}
}
})
</script>
</body>
vue单元素的过渡
Vue 提供了 transition的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
<body>
<div id='app'>
<button @click='show=!show'>切换状态</button>
<transition name='fade'>
<p v-if='show'>hello world</p>
</transition>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
show: true
}
})
</script>
</body>
当插入或删除包含在transition 组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数( 就是在一个生命周期的各个阶段给我们提供的操作切入口 ),这些钩子函数将在恰当的时机被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)
<style>
.v-enter {
opacity: 0;
}
.v-enter-active {
transition: opacity 2s ease;
}
.v-enter-to {
opacity: 1;
}
.v-leave {
opacity: 1;
}
.v-leave-active {
transition: opacity 2s ease;
}
.v-leave-to {
opacity: 0;
}
</style>
<body>
<div id='app'>
<!-- <div v-if='show'>
<h1>你好,世界</h1>
</div>
<button @click='show=!show'>按钮</button> -->
<button @click='show=!show'>按钮</button>
<transition>
<div v-if='show'>
<h1>你好,世界</h1>
</div>
</transition>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
show: true
}
})
</script>
</body>
在进入/离开的过渡中,会有6 个 class切换。
1.v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
2.v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
3.v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效(与此同时 v-enter被移除),在过渡/动画完成之后移除。
4.v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
5.v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
6.v-leave-to:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效(与此同时 v-leave被删除),在过渡/动画完成之后移除。
vue单元素动画
下面我们可以试试使用主动式的动画, CSS 主动式动画用法同 CSS 过渡
<style>
@keyframes move {
0% {
transform: translateX(-100px);
}
50% {
transform: translateX(-50px);
}
100% {
transform: translateX(0);
}
}
.v-enter-active {
animation: move 2s linear;
}
.v-leave-active {
animation: move 2s reverse;
}
</style>
<body>
<div id='app'>
<button @click='show=!show'>按钮</button>
<transition>
<h1 v-if='show'>哈哈哈</h1>
</transition>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
show: false
}
})
</script>
</body>
主动式动画比起触发式动画, 少了enter, leave-to的定义.但是这个定义可以在关键帧动画里面进行定义.
enter就是0% 的帧,enter-to就是100%的帧
leave就是100%的帧, leave-to就是0%的帧(就是动画倒过来)
单元素/组件的动画应用自定义过渡的类名
我们可以通过以下特性来自定义过渡类名:
enter-class
enter-active-class
enter-to-class
leave-class
leave-active-class
leave-to-class
他们的优先级高于普通的类名,这对于 结合一起使用Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 或是layui的CSS动画体系结合使用十分有用。
<style>
@keyframes move {
0% {
transform: translateX(-100px);
}
50% {
transform: translateX(-50px);
}
100% {
transform: translateX(0);
}
}
.fade-enter-active {
animation: move 2s linear;
}
.fade-leave-active {
animation: move 2s reverse;
}
.come {
animation: move 2s linear;
}
.out{
animation: move 2s reverse;
}
</style>
<body>
<div id='app'>
<button @click=fn>按钮</button>
<transition>
<h1 v-if='show'>哈哈哈</h1>
</transition>
<hr>
<transition name='fade'>
<h1 v-if='show'>哈哈哈</h1>
</transition>
<hr>
<transition name='laowang' enter-active-class='come' leave-active-class='out'>
<h1 v-if='show'>嘿嘿嘿</h1>
</transition>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
show: false,
},
methods: {
fn() {
this.show = !this.show
}
}
})
</script>
</body>
简单来说,原理和上面我们自己写的动画一毛一样, 就只是把自己做的动画代码改成别人的了.
多元素的过渡
<div id='app'>
<button @click='show=!show'>按钮</button>
<transition name='fade'>
<div v-if='show'>
<h1>hello world</h1>
</div>
<div v-if='show'>
<h1>你好,世界</h1>
</div>
</transition>
</div>
这样是不行滴,transition标签里面是不阔以直接放俩元素的, 这样的话, 第二个元素会直接忽略,而且还会有一个警告
<div id='app'>
<button @click='show=!show'>按钮</button>
<transition name='fade'>
<div v-if='show'>
<h1>hello world</h1>
</div>
<div v-else>
<h1>你好,世界</h1>
</div>
</transition>
</div>
这样也是不行滴, 虽说咱们现在里面一次只会出现一个元素, 但是这两个元素是同名标签, 所以只是单纯的出现了元素内容的替换,木有动画效果
当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 组件中的多个元素设置 key 是一个更好的实践。
<transition name='fade'>
<div v-if='show' key='one'>
<h1>hello world</h1>
</div>
<div v-else key='two'>
<h1>你好,世界</h1>
</div>
</transition>
这样的多个元素的显示效果之间的过渡, 是存在一个重叠的时间的上一个元素的消失和下一个元素的显示, 这两个动画是同步进行的, 所以看上去像是有两个元素都显示出来了
在一些场景中,也可以通过给同一个元素的 key 特性设置不同的状态来代替 v-if 和 v-else,上面的例子可以重写为下面的示例, 效果是一样的:
<transition name='fade'>
<div :key='show'>
<h1>{{show?'hello world':'你好,世界'}}</h1>
</div>
</transition>
多元素的过渡状态重叠的解决方案
这种写法会导致两个元素在某一时刻同时出现
<transition name='fade'>
<div v-if='show' key='one'>
<h1>hello world</h1>
</div>
<div v-else key='two'>
<h1>你好,世界</h1>
</div>
</transition>
同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 给transition标签提供了 过渡模式
- in-out:新元素先进行过渡,完成之后当前元素过渡离开
- out-in:当前元素先进行过渡,完成之后新元素过渡进入
<transition name='fade' mode='in-out'>
<div v-if='show' key='one'>
<h1>hello world</h1>
</div>
<div v-else key='two'>
<h1>你好,世界</h1>
</div>
</transition>
多组件的过渡
多个组件的过渡简单很多 - 我们不需要使用key 特性。相反,我们只需要使用动态组件
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 2s ease;
}
</style>
<body>
<div id='app'>
<button @click='fn(tab)' v-for='tab in tabs' :key='tab'>{{tab}}</button>
<transition name='fade' , mode='out-in'>
<component :is='now_tab'></component>
</transition>
<!-- <component is='tab_zhuye'></component> -->
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
tabs: ['zhuye', 'keji', 'yinyue'],
now_tab: 'tab_zhuye'
},
methods: {
fn(tab) {
return this.now_tab = 'tab_' + tab
}
},
components: {
tab_zhuye: {
template: '<div><h1>主页组件</h1></div>'
},
tab_keji: {
template: '<div><h1>科技组件</h1></div>'
},
tab_yinyue: {
template: '<div><h1>音乐组件</h1></div>'
},
}
})
</script>
</body>
列表的过渡
怎么同时渲染整个列表,比如使用v-for ?在这种场景中,使用 组件
在我们深入例子之前,先了解关于这个组件的几个特点:
- 不同于 ,它会以一个真实元素呈现:默认为一个 。你也可以通过 tag 特性更换为其他元素。( 注意, 此处是指 transition-group元素本身)
- 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素 总是需要 提供唯一的 key 属性值。
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
<style>
span {
display: inline-block;
margin-right: 20px;
font-size: 30px;
}
.v-enter {
opacity: 0;
transform: translateY(30px);
}
.v-enter-active {
transition: 1s ease-in;
}
.v-enter-to {
opacity: 1;
transform: translateY(0);
}
</style>
<body>
<div id='app'>
<div>
<transition-group>
<span v-for='item in list' :key='item'>
{{item}}
</span>
</transition-group>
<button @click='fn'>add</button>
</div>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [1, 2, 3],
},
methods: {
fn() {
this.list.push(this.list.length + 1)
}
}
})
</script>
</body>
数据的过渡
Vue 的过渡系统提供了非常多简单的方法设置进入、离开和列表的动效。那么对于数据元素本身的动效呢,比如:
1.数字和运算
2.颜色的显示
3.节点的位置
4.元素的大小和其他的属性
这些数据要么本身就以数值形式存储,要么可以转换为数值。有了这些数值后,我们就可以结合Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态。
<body>
<div id='app'>
<button @click='fn'>按钮</button>
<div>{{animate_num}}</div>
</div>
<script src='./js/vue.js'></script>
<script>
const app = new Vue({
el: '#app',
data: {
num: 1,
animate_num: 1
},
methods: {
fn() {
this.num = 10
if (this.animate_num < this.num) {
const set_id = setInterval(() => {
this.animate_num += 1;
if (this.animate_num === this.num) {
clearInterval(set_id)
}
}, 100);
}
}
}
})
</script>
</body>