Vue 动画
Vue 中内置的 <Transition> 组件可以帮助我们在使用 v-if, v-show 或动态组件添加或删除元素时制作动画。
在其他情况下使用纯 CSS 转换和动画并没有错。
CSS 转换和动画简介
但是,在我们使用 Vue 特定的内置 <Transition> 组件来创建动画之前,让我们看看如何将普通 CSS 动画和转换与 Vue 一起使用的两个实例。
实例
App.vue:
<template><h1>Basic CSS Transition</h1><button @click="this.doesRotate = true">Rotate</button><div :class="{ rotate: doesRotate }"></div></template><script>export default {data() {return {doesRotate: false}}}</script><style scoped>.rotate {rotate: 160deg;transition: rotate 1s;}div {border: solid black 2px;background-color: lightcoral;width: 60px;height: 60px;}h1, button, div {margin: 10px;}</style>
在上面的例子中,我们使用 v-bind 为 <div> 标记提供一个类,以便它旋转。旋转需要 1 秒的原因是它是用 CSS 转换属性定义的。
在下面的实例中,我们将看到如何使用 CSS animation 属性移动对象。
实例
App.vue:
<template><h1>Basic CSS Animation</h1><button @click="this.doesMove = true">Start</button><div :class="{ move: doesMove }"></div></template><script>export default {data() {return {doesMove: false}}}</script><style scoped>.move {animation: move .5s alternate 4 ease-in-out;}@keyframes move {from {translate: 0 0;}to {translate: 70px 0;}}div {border: solid black 2px;background-color: lightcoral;border-radius: 50%;width: 60px;height: 60px;}h1, button, div {margin: 10px;}</style>
<Transition> 组件
使用简单的 CSS 转换和动画并没有错,就像我们在上面两个例子中所做的那样。
但幸运的是,Vue 为我们提供了内置的 <Transition> 组件,用于在元素从我们的应用程序中删除或添加时使用 v-if 或 v-show 对其进行动画处理的情况,因为这在使用纯 CSS 动画时很难做到。
让我们首先制作一个按钮添加或删除 <p> 标记的应用程序:
实例
App.vue:
<template><h1>Add/Remove <p> Tag</h1><button @click="this.exists = !this.exists">{{btnText}}</button><br><p v-if="exists">Hello World!</p></template><script>export default {data() {return {exists: false}},computed: {btnText() {if(this.exists) {return 'Remove';}else {return 'Add';}}}}</script><style>p {background-color: lightgreen;display: inline-block;padding: 10px;}</style>
现在,让我们将 <Transition> 组件包裹在 <p> 标记周围,并查看如何设置移除 <p> 标签的动画。
当我们使用 <Transition> 组件时,我们会自动获得六个不同的 CSS 类,当添加或删除元素时,我们可以使用这些类来设置动画。
在下面的实例中,我们将使用自动可用的类 v-leave-from 和 v-leave-to 在移除 <p> 标记时制作淡出动画:
实例
App.vue:
<template><h1>Add/Remove <p> Tag</h1><button @click="this.exists = !this.exists">{{btnText}}</button><br><Transition><p v-if="exists">Hello World!</p></Transition></template><script>export default {data() {return {exists: false}},computed: {btnText() {if(this.exists) {return 'Remove';}else {return 'Add';}}}}</script><style>.v-leave-from {opacity: 1;}.v-leave-to {opacity: 0;}p {background-color: lightgreen;display: inline-block;padding: 10px;transition: opacity 0.5s;}</style>
6 个 <Transition> 类
当我们使用 <Transition> 组件时,有 6 个类自动可用。
添加 <Transition> 组件中的元素后,我们可以使用前 3 个类来设置该转换的动画:
- v-enter-from
- v-enter-active
- v-enter-to
当 <Transition> 组件中的一个元素被 移除 时,我们可以使用下面 3 个类:
- v-leave-from
- v-leave-active
- v-leave-to
现在,让我们使用其中的 4 个类,这样我们就可以在添加 <p> 标记时和移除 <p> 标签时都设置动画。
实例
App.vue:
<template><h1>Add/Remove <p> Tag</h1><button @click="this.exists = !this.exists">{{btnText}}</button><br><Transition><p v-if="exists">Hello World!</p></Transition></template><script>export default {data() {return {exists: false}},computed: {btnText() {if(this.exists) {return 'Remove';}else {return 'Add';}}}}</script><style>.v-enter-from {opacity: 0;translate: -100px 0;}.v-enter-to {opacity: 1;translate: 0 0;}.v-leave-from {opacity: 1;translate: 0 0;}.v-leave-to {opacity: 0;translate: 100px 0;}p {background-color: lightgreen;display: inline-block;padding: 10px;transition: all 0.5s;}</style>
我们还可以使用 v-enter-active 和 v-leave-active 在添加或删除元素期间设置样式或动画:
实例
App.vue:
<template><h1>Add/Remove <p> Tag</h1><button @click="this.exists = !this.exists">{{btnText}}</button><br><Transition><p v-if="exists">Hello World!</p></Transition></template><script>export default {data() {return {exists: false}},computed: {btnText() {if(this.exists) {return 'Remove';}else {return 'Add';}}}}</script><style>.v-enter-active {background-color: lightgreen;animation: added 1s;}.v-leave-active {background-color: lightcoral;animation: added 1s reverse;}@keyframes added {from {opacity: 0;translate: -100px 0;}to {opacity: 1;translate: 0 0;}}p {display: inline-block;padding: 10px;border: dashed black 1px;}</style>
转换命名 prop
如果您有多个 <Transition> 组件,但希望至少有一个 <Transition> 组件具有不同的动画,则需要为 <Transition> 组件使用不同的名称来区分它们。
我们可以使用命名 Prop 选择 <Transition> 组件的名称,这也会更改转换类的名称,这样我们就可以为该组件设置不同的 CSS 动画规则。
<Transition name="swirl">
如果转换命名 prop 值设置为 'swirl',则自动可用的类现在将以 'swirl' 而不是 'v-' 开头:
- swirl-enter-from
- swirl-enter-active
- swirl-enter-to
- swirl-leave-from
- swirl-leave-active
- swirl-leave-to
在下面的实例中,我们使用命名 prop 为 <Transition> 组件提供不同的动画。一个 <Transition> 组件没有命名,因此使用以 'v-' 开头的自动生成的 CSS 类为其提供动画。另一个 <Transition> 组件被赋予了一个名称 'swirl',这样它就可以被赋予以 'swirl-' 开头的自动生成的 CSS 类的动画规则。
实例
App.vue:
<template><h1>Add/Remove <p> Tag</h1><p>The second transition in this example has the name prop "swirl", so that we can keep the transitions apart with different class names.</p><hr><button @click="this.p1Exists = !this.p1Exists">{{btn1Text}}</button><br><Transition><p v-if="p1Exists" id="p1">Hello World!</p></Transition><hr><button @click="this.p2Exists = !this.p2Exists">{{btn2Text}}</button><br><Transition name="swirl"><p v-if="p2Exists" id="p2">Hello World!</p></Transition></template><script>export default {data() {return {p1Exists: false,p2Exists: false}},computed: {btn1Text() {if(this.p1Exists) {return 'Remove';}else {return 'Add';}},btn2Text() {if(this.p2Exists) {return 'Remove';}else {return 'Add';}}}}</script><style>.v-enter-active {background-color: lightgreen;animation: added 1s;}.v-leave-active {background-color: lightcoral;animation: added 1s reverse;}@keyframes added {from {opacity: 0;translate: -100px 0;}to {opacity: 1;translate: 0 0;}}.swirl-enter-active {animation: swirlAdded 1s;}.swirl-leave-active {animation: swirlAdded 1s reverse;}@keyframes swirlAdded {from {opacity: 0;rotate: 0;scale: 0.1;}to {opacity: 1;rotate: 360deg;scale: 1;}}#p1, #p2 {display: inline-block;padding: 10px;border: dashed black 1px;}#p2 {background-color: lightcoral;}</style>
JavaScript Transition 钩子
刚才提到的每个 Transition 类都对应一个事件,我们可以挂接该事件来运行一些 JavaScript 代码。
| Transition 类 | JavaScript 事件 |
|---|---|
| v-enter-from | before-enter |
| v-enter-active | enter |
| v-enter-to | after-enter enter-cancelled |
| v-leave-from | before-leave |
| v-leave-active | leave |
| v-leave-to | after-leave leave-cancelled (v-show only) |
当在下面的实例中发生 after-enter 事件时,将运行一个显示红色 <div> 元素的方法。
实例
App.vue:
<template><h1>JavaScript Transition Hooks</h1><p>This code hooks into "after-enter" so that after the initial animation is done, a method runs that displays a red div.</p><button @click="pVisible=true">Create p-tag!</button><br><Transition @after-enter="onAfterEnter"><p v-show="pVisible" id="p1">Hello World!</p></Transition><br><div v-show="divVisible">This appears after the "enter-active" phase of the transition.</div></template><script>export default {data() {return {pVisible: false,divVisible: false}},methods: {onAfterEnter() {this.divVisible = true;}}}</script><style scoped>.v-enter-active {animation: swirlAdded 1s;}@keyframes swirlAdded {from {opacity: 0;rotate: 0;scale: 0.1;}to {opacity: 1;rotate: 360deg;scale: 1;}}#p1, div {display: inline-block;padding: 10px;border: dashed black 1px;}#p1 {background-color: lightgreen;}div {background-color: lightcoral;}</style>
您可以使用以下实例中的 "Toggle" 按钮来中断 <p> 元素的 enter-cancelled 阶段,从而触发输入取消事件:
实例
App.vue:
<template><h1>The 'enter-cancelled' Event</h1><p>Click the toggle button again before the enter animation is finished to trigger the 'enter-cancelled' event.</p><button @click="pVisible=!pVisible">Toggle</button><br><Transition @enter-cancelled="onEnterCancelled"><p v-if="pVisible" id="p1">Hello World!</p></Transition><br><div v-if="divVisible">You interrupted the "enter-active" transition.</div></template><script>export default {data() {return {pVisible: false,divVisible: false}},methods: {onEnterCancelled() {this.divVisible = true;}}}</script><style scoped>.v-enter-active {animation: swirlAdded 2s;}@keyframes swirlAdded {from {opacity: 0;rotate: 0;scale: 0.1;}to {opacity: 1;rotate: 720deg;scale: 1;}}#p1, div {display: inline-block;padding: 10px;border: dashed black 1px;}#p1 {background-color: lightgreen;}div {background-color: lightcoral;}</style>
显示 Prop
如果我们有一个元素要在页面加载时设置动画,我们需要在 <Transition> 组件上使用显示 Prop。
<Transition appear>...</Transition>
在本例中,当页面首次加载时,显示 prop 将启动动画:
实例
App.vue:
<template><h1>The 'appear' Prop</h1><p>The 'appear' prop starts the animation when the p tag below is rendered for the first time as the page opens. Without the 'appear' prop, this example would have had no animation.</p><Transition appear><p id="p1">Hello World!</p></Transition></template><style>.v-enter-active {animation: swirlAdded 1s;}@keyframes swirlAdded {from {opacity: 0;rotate: 0;scale: 0.1;}to {opacity: 1;rotate: 360deg;scale: 1;}}#p1 {display: inline-block;padding: 10px;border: dashed black 1px;background-color: lightgreen;}</style>
元素间的转换
<Transition> 组件也可以用于在多个元素之间切换,只要我们使用 <v-if> 和 <v-else-if> 确保一次只显示一个元素:
实例
App.vue:
<template><h1>Transition Between Elements</h1><p>Click the button to get a new image.</p><p>The new image is added before the previous is removed. We will fix this in the next example with mode="out-in".</p><button @click="newImg">Next image</button><br><Transition><img src="/img_pizza.svg" v-if="imgActive === 'pizza'"><img src="/img_apple.svg" v-else-if="imgActive === 'apple'"><img src="/img_cake.svg" v-else-if="imgActive === 'cake'"><img src="/img_fish.svg" v-else-if="imgActive === 'fish'"><img src="/img_rice.svg" v-else-if="imgActive === 'rice'"></Transition></template><script>export default {data() {return {imgActive: 'pizza',imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],indexNbr: 0}},methods: {newImg() {this.indexNbr++;if(this.indexNbr >= this.imgs.length) {this.indexNbr = 0;}this.imgActive = this.imgs[this.indexNbr];}}}</script><style>.v-enter-active {animation: swirlAdded 1s;}.v-leave-active {animation: swirlAdded 1s reverse;}@keyframes swirlAdded {from {opacity: 0;rotate: 0;scale: 0.1;}to {opacity: 1;rotate: 360deg;scale: 1;}}img {width: 100px;margin: 20px;}img:hover {cursor: pointer;}</style>
mode="out-in"
在上面的实例中,在删除前一个图像之前添加下一个图像。我们在 <Transition> 组件上使用 mode="out-in" prop 和 prop 值,以便在添加下一个元素之前完成元素的移除。
实例
除了 mode="out-in" 之外,此实例还使用计算值 'imgActive',而不是我们在上一实例中使用的 'newImg' 方法。
App.vue:
<template><h1>mode="out-in"</h1><p>Click the button to get a new image.</p><p>With mode="out-in", the next image is not added until the current image is removed. Another difference from the previous example, is that here we use computed prop instead of a method.</p><button @click="indexNbr++">Next image</button><br><Transition mode="out-in"><img src="/img_pizza.svg" v-if="imgActive === 'pizza'"><img src="/img_apple.svg" v-else-if="imgActive === 'apple'"><img src="/img_cake.svg" v-else-if="imgActive === 'cake'"><img src="/img_fish.svg" v-else-if="imgActive === 'fish'"><img src="/img_rice.svg" v-else-if="imgActive === 'rice'"></Transition></template><script>export default {data() {return {imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],indexNbr: 0}},computed: {imgActive() {if(this.indexNbr >= this.imgs.length) {this.indexNbr = 0;}return this.imgs[this.indexNbr];}}}</script><style>.v-enter-active {animation: swirlAdded 0.7s;}.v-leave-active {animation: swirlAdded 0.7s reverse;}@keyframes swirlAdded {from {opacity: 0;rotate: 0;scale: 0.1;}to {opacity: 1;rotate: 360deg;scale: 1;}}img {width: 100px;margin: 20px;}img:hover {cursor: pointer;}</style>
动态组件的转换
我们还可以使用 <Transition> 组件来设置动态组件之间切换的动画:
实例
App.vue:
<template><h1>Transition with Dynamic Components</h1><p>The Transition component wraps around the dynamic component so that the switching can be animated.</p><button @click="toggleValue = !toggleValue">Switch component</button><Transition mode="out-in"><component :is="activeComp"></component></Transition></template><script>export default {data () {return {toggleValue: true}},computed: {activeComp() {if(this.toggleValue) {return 'comp-one'}else {return 'comp-two'}}}}</script><style>.v-enter-active {animation: slideIn 0.5s;}@keyframes slideIn {from {translate: -200px 0;opacity: 0;}to {translate: 0 0;opacity: 1;}}.v-leave-active {animation: slideOut 0.5s;}@keyframes slideOut {from {translate: 0 0;opacity: 1;}to {translate: 200px 0;opacity: 0;}}#app {width: 350px;margin: 10px;}#app > div {border: solid black 2px;padding: 10px;margin-top: 10px;}</style>