Vue.js第4课-Vue中的动画特效(part02)
四、Vue 中的 Js 动画与 Velocity.js 的结合
1、js 动画
以前讲动画的时候都是通过 css 来实现一个动画效果,那有办法通过 js 实现一些动画效果么?其实也是有的。Vue 给我们提供了很多 js 动画的钩子,我们可以这样去用:
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnetr" @after-enter="handleAfterEnetr">
<div v-show="show">Hello World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
},
handleBeforeEnter: function (el) {
el.style.color = "red"
},
handleEnetr: function (el, done) {
setTimeout(function () {
el.style.color = "blue";
done();
}, 2000);
},
handleAfterEnetr: function (el) {
el.style.color = "black"
}
}
})
</script>
上面代码,在 transition 中绑定了一些事件 @before-enetr、@enetr、@after-enter,并在 methods 中编写这些函数,这些函数都会接受一个参数 el,这个 el 指的就是 transition 动画包裹的这个标签,那就直接可以通过 el.style 给他设置样式了。
再来看第二个钩子函数 enter,这个钩子和 before-enter 稍有差异,他会接受两个参数,第一个是 el,第二个是一个 done 回调函数,enter 这个钩子当 before-enter 被触发结束后,下一步就是要真正的运行动画了,当真正运行动画的时候,实际上所有的动画都写在 enter 这个钩子对应的函数中。例如上面我在 handleEnter 中通过延时器编写一个 2s 后变色的效果,此时,页面的一个效果就是:点击显示后,元素的颜色开始是红色,接着动画执行,2s 后变为蓝色。在 handleEnetr 这个函数中,done 这个回调函数也很重要,当动画结束的时候,要手动的调一下 done 这个回调函数,相当于你告诉 Vue 这个动画已经执行完了。
当 done 被调用之后,Vue 又会触发一个事件,这个事件的名字叫 after-enter,可以写一个对应的处理函数,叫做 handleAfterEnetr,这个函数中,我们可以让元素的颜色恢复成黑色。但是此时页面上的效果是他没有变蓝,而是直接变成了黑色,这是因为在 handleEnetr 中,我们设置了 2s 动画就结束了,红色变为蓝色后,马上就变为了黑色,为了看的更清楚一些,可以这么写:
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnetr" @after-enter="handleAfterEnetr">
<div v-show="show">Hello World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
},
handleBeforeEnter: function (el) {
el.style.color = "red"
},
handleEnetr: function (el, done) {
setTimeout(function () {
el.style.color = "blue";
}, 2000);
setTimeout(function () {
done();
}, 4000)
},
handleAfterEnetr: function (el) {
el.style.color = "black";
}
}
})
</script>
上边设置了 4s 后动画才结束,所以此时页面上的效果就是先变红,2S 后变蓝,再过两秒后变黑,动画结束。上面讲了入场动画,那么出场动画呢?出场动画的钩子函数是 before-leave、leave、after、leave,他们的用法和 enter 是一致的,我们可以做个测试:
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" @before-leave="handleBeforeLeave" @leave="handleLeave" @after-leave="handleAfterLeave">
<div v-show="show">Hello World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
},
handleBeforeLeave: function (el) {
el.style.color = "red"
},
handleLeave: function (el, done) {
setTimeout(function () {
el.style.color = "blue";
}, 2000);
setTimeout(function () {
done();
}, 4000)
},
handleAfterLeave: function (el) {
el.style.color = "black";
}
}
})
</script>
以上就是实现了 js 的动画,接下来介绍一种 js 常用的一个动画库。
2、Velocity.js 动画库
去 Velocity.js 官网 下载并查看 API 文档。我这里直接用 BootCDN 方式引用了。
下来我们借助 Velocity.js 来实现一些动画效果。首先把我们自己写的动画效果删掉,当动画还没执行时,我们在 handleBeforeEnter 先设置元素的 opacity 为 0,也就是还没有入场的时候透明度为 0,动画开始执行,走到 handleEnetr 的时候,我希望将元素的透明度慢慢变为 1,通过 Velocity() 来写,第一个参数传 el,第二个参数写变化的结果,第三个参数写这个动画变化的时间,例如:Velocity(el,{opacity:1},{duration:1000})。
<script src='https://cdn.bootcss.com/velocity/2.0.5/velocity.js'></script>
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnetr" @after-enter="handleAfterEnetr">
<div v-show="show">Hello World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
},
handleBeforeEnter: function (el) {
el.style.opacity = 0
},
handleEnetr: function (el, done) {
Velocity(el, {
opacity: 1
}, {
duration: 1000,
complete: done
});
},
handleAfterEnetr: function (el) {
console.log("动画结束!")
}
}
})
</script>
此时到页面上来看一下,从隐藏到显示,会有一个渐现的效果。但是这个效果这么执行完其实是有问题的,我们先看一下 Velocity 的官网,在右侧导航找到一个 Complete 参数:
这里有一个 complete 配置参数,我们在 duration 后面加一个 complete 配置参数,里面加一个 done 函数,他的意思是,当 Velocity 执行完这个动画之后,complete 这个属性对应的内容会被自动的执行,也就是 done 这个函数会被自动的执行,告诉 Vue 我的动画执行完成了,接下来 handleAfterEnet 这个函数就会被执行,可以让他打印一个 “动画结束!”。可以试一下,如果不设置 complete 这个参数,那么他就永远不会执行 handleAfterEnet 这个方法,也就永远不会结束。
以上就讲解了 Vue 中如何使用 js 的钩子实现 js 动画效果,和 Velocity.js 动画库的使用。
五、Vue 中多个元素或组件的过渡
1、多个元素的过渡
之前讲的是一个元素的过渡动画,这一节来讲一下 Vue 中多个元素或组件的过渡。
在 transition 这个标签内,我们再加一个元素,通过 v-if 和 v-else 的形式来确定显示与否,然后再添加一个过渡动画,希望有一个渐隐渐现的效果:
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enetr-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade">
<div v-if="show">Hello World!</div>
<div v-else>Bye World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
}
}
})
</script>
但是我们打开页面,切换效果是有的,但是渐隐渐现的效果并没有实现,原因是,Vue 在这两个元素进行切换的时候,他会尽量的复用 DOM,所以导致这种动画不会出现,如果让 Vue 不去复用 DOM 的话,该怎么解决呢?我们回忆一下 Vue.js第2课中的第7章讲过的 key 值,在那个例子中,有两个 input 框,因为 Vue 中 DOM 复用的机制,导致在控制台修改 data 后,input 框切换了,但是里边的值还是保持着上一个输入框输入的内容,我们通过给每一个 input 框设置一个属于自身的 key 值,就可以解决这个问题,这里也一样,给两个元素分别设置一个 key 值。
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enetr-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade">
<div v-if="show" key="hello">Hello World!</div>
<div v-else key="bye">Bye World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
}
}
})
</script>
此时打开页面,可以看到渐隐渐现动画效果也实现了。
实际上 Vue 在 transition 上还给我们提供了一个参数 mode,来设置多个属性切换时的一个效果,如果把 mode 设置为 in-out,就是要显示的元素先显示,要隐藏的元素再隐藏,反过来,out-in 就是要隐藏的元素先隐藏,要显示的元素再显示,看下边代码:
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enetr-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" mode="out-in">
<div v-if="show" key="hello">Hello World!</div>
<div v-else key="bye">Bye World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show
}
}
})
</script>
以上就是对个元素的过渡,下面在来看一下多个组件的过渡。
2、多个组件的过渡
创建两个组件,并在 transition 中使用这两个组件:
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" mode="out-in">
<hello v-if="show"></hello>
<bye v-else></bye>
</transition>
</div>
<script>
Vue.component("hello", {
template: "<div>Hello World!</div>"
})
Vue.component("bye", {
template: "<div>Bye world!</div>"
})
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleFun: function () {
this.show = !this.show;
}
}
})
</script>
打开页面,可以看到渐隐渐现的动画效果是没有问题的,这样就实现了多个组件的过渡效果,如果改为动态组件呢?
我们先来回忆一下 Vue.js第3课第八章中讲过的动态组件,如果兄弟组件之间是通过 v-if、v-else 的方式进行判断显示的,那就可以使用 component 标签添加 :is 属性和判断条件,来显示不同的组件。接下来按照动态组件的方式修改一下上面代码。
先来修改一下 data,可以将之前的 show 换为 type,值写成子组件名,methods 中的 toggleFun 中的代码也需要修改一下:
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleFun">toggle</button>
<transition name="fade" mode="out-in">
<component :is="type"></component>
</transition>
</div>
<script>
Vue.component("hello", {
template: "<div>Hello World!</div>"
})
Vue.component("bye", {
template: "<div>Bye world!</div>"
})
var app = new Vue({
el: "#app",
data: {
type: "hello"
},
methods: {
toggleFun: function () {
this.type = this.type === "hello" ? "bye" : "hello"
}
}
})
</script>
六、Vue 中的列表过渡
我们先来实现这样一个功能:显示一个列表项,这个列表项循环显示数组中的内容,这个数组里的内容是通过点击一次按钮,加一次数据。
<div id="app">
<button @click="clickAddFun">Add</button>
<div v-for="item of list" :key="item.id">{{item.title}}</div>
</div>
<script>
var count = 0;
var app = new Vue({
el: "#app",
data: {
list: []
},
methods: {
clickAddFun: function () {
this.list.push({
id: count++,
title: "Hello World!"
})
}
}
})
</script>
打开页面,可以看到,每点一次按钮,就会新增一个 “Hello World!”。以前我们做动画效果的时候,只能做对单个元素的隐藏或显示,或者说对多个元素来回切换做隐藏或显示效果。那怎么对列表做一个过渡效果呢?
这个时候给大家讲解一个新的标签,叫做 transition-group,我们把要循环的元素放到这个标签中,然后需要写一个 css 过渡的内容,方法和之前一样:
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-to {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="clickAddFun">Add</button>
<transition-group>
<div v-for="item of list" :key="item.id">{{item.title}}</div>
</transition-group>
</div>
<script>
var count = 0;
var app = new Vue({
el: "#app",
data: {
list: []
},
methods: {
clickAddFun: function () {
this.list.push({
id: count++,
title: "Hello World!"
})
}
}
})
</script>
打开页面,可以看到每次点击后添加内容的时候都有一个渐现的效果,它的原理是什么呢?其实可以这样理解:
<transition-group>
<div>Hello World!</div>
<div>Hello World!</div>
<div>Hello World!</div>
</transition-group>
<!-- 相当于: -->
<transition>
<div>Hello World!</div>
</transition>
<transition>
<div>Hello World!</div>
</transition>
<transition>
<div>Hello World!</div>
</transition>
给列表外层加一个 transition-group 标签,相当于给列表的每一项都加一个 transition 标签。
七、Vue 中的动画封装
这一节来看一下如何进行 Vue 中的动画封装,让我们的动画变得可复用。
1、封装 style 样式的动画
先来回忆一下之前写的通过点击 toggle 实现元素渐隐渐现的效果:
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleBtnFun">toggle</button>
<transition>
<div v-if="show">Hello World!</div>
</transition>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleBtnFun: function () {
this.show = !this.show;
}
}
})
</script>
有的时候这种渐隐渐现的效果会用的比较多,我们就希望对这种效果进行一个封装,怎么封装呢?
可以建一个组件,例如叫 fade,然后设置模板,模板里把 transition 标签放进去,在 transition 里写一个 slot,也就是我让外部给我传一些 DOM 元素,紧接着我还需要外部给我传一个 show 这样一个变量,我要根据这个变量来判断外部传过来的 DOM 是否要被显示。然后用一下 fade 这个组件,这个组件需要传几个东西,插槽里的内容已经通过 Hello World! 传给子组件了,紧接着要传一个名字叫 show 的变量,他等于父组件的这个 show 的变量。现在已经把动画封装到了 fade 组件中,如果想再用这种动画,复制出一份这个组件就可以了,然后里面传不同的 DOM 元素就可以了(这就是为什么使用 slot 的原因了)。
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="toggleBtnFun">toggle</button>
<fade :show="show">
<div>Hello World!</div>
</fade>
<fade :show="show">
<h1>Bye World!</h1>
</fade>
</div>
<script>
Vue.component("fade", {
props: ["show"],
template: `
<transition>
<slot v-if="show"></slot>
</transition>
`
})
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleBtnFun: function () {
this.show = !this.show;
}
}
})
</script>
打开页面,点击 toggle,不管是 div 这个元素,还是 h1 这个元素,都实现了渐隐渐现的效果。其实还可以把上面 style 中的样式也封装到子组件里,回顾一下本课第4章 Vue 中的 js 动画。
2、封装 js 样式的动画
我们通过钩子函数,将动画效果都封装到子组件的 methods 方法中:
<div id="app">
<button @click="toggleBtnFun">toggle</button>
<fade :show="show">
<div>Hello World!</div>
</fade>
<fade :show="show">
<h1>Bye World!</h1>
</fade>
</div>
<script>
Vue.component("fade", {
props: ["show"],
template: `
<transition
@before-enter="handleBeforeEnter" @enter="handleEnter" @after-enter="handleAfterEnter">
<slot v-if="show"></slot>
</transition>
`,
methods: {
handleBeforeEnter: function (el) {
el.style.color = "red";
},
handleEnter: function (el, done) {
setTimeout(function () {
el.style.color = "blue";
}, 2000);
setTimeout(function () {
done();
}, 4000);
},
handleAfterEnter: function (el) {
el.style.color = "black";
console.log("动画结束!");
}
}
})
var app = new Vue({
el: "#app",
data: {
show: true
},
methods: {
toggleBtnFun: function () {
this.show = !this.show;
}
}
})
</script>
打开页面可以看到,元素显示后,显示红色,2s 后变为蓝色,再 2s 后变为黑色,动画结束。