Vue 动画

Vue 中内置的 <Transition> 组件可以帮助我们在使用 v-if, v-show 或动态组件添加或删除元素时制作动画。

在其他情况下使用纯 CSS 转换和动画并没有错。


CSS 转换和动画简介

本教程的这一部分需要了解基本的 CSS 动画转换

但是,在我们使用 Vue 特定的内置 <Transition> 组件来创建动画之前,让我们看看如何将普通 CSS 动画和转换与 Vue 一起使用的两个实例。

实例

App.vue:

  1. <template>
  2. <h1>Basic CSS Transition</h1>
  3. <button @click="this.doesRotate = true">Rotate</button>
  4. <div :class="{ rotate: doesRotate }"></div>
  5. </template>
  6. <script>
  7. export default {
  8. data() {
  9. return {
  10. doesRotate: false
  11. }
  12. }
  13. }
  14. </script>
  15. <style scoped>
  16. .rotate {
  17. rotate: 160deg;
  18. transition: rotate 1s;
  19. }
  20. div {
  21. border: solid black 2px;
  22. background-color: lightcoral;
  23. width: 60px;
  24. height: 60px;
  25. }
  26. h1, button, div {
  27. margin: 10px;
  28. }
  29. </style>

在上面的例子中,我们使用 v-bind<div> 标记提供一个类,以便它旋转。旋转需要 1 秒的原因是它是用 CSS 转换属性定义的。

在下面的实例中,我们将看到如何使用 CSS animation 属性移动对象。

实例

App.vue:

  1. <template>
  2. <h1>Basic CSS Animation</h1>
  3. <button @click="this.doesMove = true">Start</button>
  4. <div :class="{ move: doesMove }"></div>
  5. </template>
  6. <script>
  7. export default {
  8. data() {
  9. return {
  10. doesMove: false
  11. }
  12. }
  13. }
  14. </script>
  15. <style scoped>
  16. .move {
  17. animation: move .5s alternate 4 ease-in-out;
  18. }
  19. @keyframes move {
  20. from {
  21. translate: 0 0;
  22. }
  23. to {
  24. translate: 70px 0;
  25. }
  26. }
  27. div {
  28. border: solid black 2px;
  29. background-color: lightcoral;
  30. border-radius: 50%;
  31. width: 60px;
  32. height: 60px;
  33. }
  34. h1, button, div {
  35. margin: 10px;
  36. }
  37. </style>

<Transition> 组件

使用简单的 CSS 转换和动画并没有错,就像我们在上面两个例子中所做的那样。

但幸运的是,Vue 为我们提供了内置的 <Transition> 组件,用于在元素从我们的应用程序中删除或添加时使用 v-ifv-show 对其进行动画处理的情况,因为这在使用纯 CSS 动画时很难做到。

让我们首先制作一个按钮添加或删除 <p> 标记的应用程序:

实例

App.vue:

  1. <template>
  2. <h1>Add/Remove <p> Tag</h1>
  3. <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  4. <p v-if="exists">Hello World!</p>
  5. </template>
  6. <script>
  7. export default {
  8. data() {
  9. return {
  10. exists: false
  11. }
  12. },
  13. computed: {
  14. btnText() {
  15. if(this.exists) {
  16. return 'Remove';
  17. }
  18. else {
  19. return 'Add';
  20. }
  21. }
  22. }
  23. }
  24. </script>
  25. <style>
  26. p {
  27. background-color: lightgreen;
  28. display: inline-block;
  29. padding: 10px;
  30. }
  31. </style>

现在,让我们将 <Transition> 组件包裹在 <p> 标记周围,并查看如何设置移除 <p> 标签的动画。

当我们使用 <Transition> 组件时,我们会自动获得六个不同的 CSS 类,当添加或删除元素时,我们可以使用这些类来设置动画。

在下面的实例中,我们将使用自动可用的类 v-leave-fromv-leave-to 在移除 <p> 标记时制作淡出动画:

实例

App.vue:

  1. <template>
  2. <h1>Add/Remove <p> Tag</h1>
  3. <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  4. <Transition>
  5. <p v-if="exists">Hello World!</p>
  6. </Transition>
  7. </template>
  8. <script>
  9. export default {
  10. data() {
  11. return {
  12. exists: false
  13. }
  14. },
  15. computed: {
  16. btnText() {
  17. if(this.exists) {
  18. return 'Remove';
  19. }
  20. else {
  21. return 'Add';
  22. }
  23. }
  24. }
  25. }
  26. </script>
  27. <style>
  28. .v-leave-from {
  29. opacity: 1;
  30. }
  31. .v-leave-to {
  32. opacity: 0;
  33. }
  34. p {
  35. background-color: lightgreen;
  36. display: inline-block;
  37. padding: 10px;
  38. transition: opacity 0.5s;
  39. }
  40. </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
注意:<Transition> 组件的根级别上只能有一个元素。

现在,让我们使用其中的 4 个类,这样我们就可以在添加 <p> 标记时和移除 <p> 标签时都设置动画。

实例

App.vue:

  1. <template>
  2. <h1>Add/Remove <p> Tag</h1>
  3. <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  4. <Transition>
  5. <p v-if="exists">Hello World!</p>
  6. </Transition>
  7. </template>
  8. <script>
  9. export default {
  10. data() {
  11. return {
  12. exists: false
  13. }
  14. },
  15. computed: {
  16. btnText() {
  17. if(this.exists) {
  18. return 'Remove';
  19. }
  20. else {
  21. return 'Add';
  22. }
  23. }
  24. }
  25. }
  26. </script>
  27. <style>
  28. .v-enter-from {
  29. opacity: 0;
  30. translate: -100px 0;
  31. }
  32. .v-enter-to {
  33. opacity: 1;
  34. translate: 0 0;
  35. }
  36. .v-leave-from {
  37. opacity: 1;
  38. translate: 0 0;
  39. }
  40. .v-leave-to {
  41. opacity: 0;
  42. translate: 100px 0;
  43. }
  44. p {
  45. background-color: lightgreen;
  46. display: inline-block;
  47. padding: 10px;
  48. transition: all 0.5s;
  49. }
  50. </style>

我们还可以使用 v-enter-activev-leave-active 在添加或删除元素期间设置样式或动画:

实例

App.vue:

  1. <template>
  2. <h1>Add/Remove <p> Tag</h1>
  3. <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  4. <Transition>
  5. <p v-if="exists">Hello World!</p>
  6. </Transition>
  7. </template>
  8. <script>
  9. export default {
  10. data() {
  11. return {
  12. exists: false
  13. }
  14. },
  15. computed: {
  16. btnText() {
  17. if(this.exists) {
  18. return 'Remove';
  19. }
  20. else {
  21. return 'Add';
  22. }
  23. }
  24. }
  25. }
  26. </script>
  27. <style>
  28. .v-enter-active {
  29. background-color: lightgreen;
  30. animation: added 1s;
  31. }
  32. .v-leave-active {
  33. background-color: lightcoral;
  34. animation: added 1s reverse;
  35. }
  36. @keyframes added {
  37. from {
  38. opacity: 0;
  39. translate: -100px 0;
  40. }
  41. to {
  42. opacity: 1;
  43. translate: 0 0;
  44. }
  45. }
  46. p {
  47. display: inline-block;
  48. padding: 10px;
  49. border: dashed black 1px;
  50. }
  51. </style>

转换命名 prop

如果您有多个 <Transition> 组件,但希望至少有一个 <Transition> 组件具有不同的动画,则需要为 <Transition> 组件使用不同的名称来区分它们。

我们可以使用命名 Prop 选择 <Transition> 组件的名称,这也会更改转换类的名称,这样我们就可以为该组件设置不同的 CSS 动画规则。

  1. <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:

  1. <template>
  2. <h1>Add/Remove <p> Tag</h1>
  3. <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>
  4. <hr>
  5. <button @click="this.p1Exists = !this.p1Exists">{{btn1Text}}</button><br>
  6. <Transition>
  7. <p v-if="p1Exists" id="p1">Hello World!</p>
  8. </Transition>
  9. <hr>
  10. <button @click="this.p2Exists = !this.p2Exists">{{btn2Text}}</button><br>
  11. <Transition name="swirl">
  12. <p v-if="p2Exists" id="p2">Hello World!</p>
  13. </Transition>
  14. </template>
  15. <script>
  16. export default {
  17. data() {
  18. return {
  19. p1Exists: false,
  20. p2Exists: false
  21. }
  22. },
  23. computed: {
  24. btn1Text() {
  25. if(this.p1Exists) {
  26. return 'Remove';
  27. }
  28. else {
  29. return 'Add';
  30. }
  31. },
  32. btn2Text() {
  33. if(this.p2Exists) {
  34. return 'Remove';
  35. }
  36. else {
  37. return 'Add';
  38. }
  39. }
  40. }
  41. }
  42. </script>
  43. <style>
  44. .v-enter-active {
  45. background-color: lightgreen;
  46. animation: added 1s;
  47. }
  48. .v-leave-active {
  49. background-color: lightcoral;
  50. animation: added 1s reverse;
  51. }
  52. @keyframes added {
  53. from {
  54. opacity: 0;
  55. translate: -100px 0;
  56. }
  57. to {
  58. opacity: 1;
  59. translate: 0 0;
  60. }
  61. }
  62. .swirl-enter-active {
  63. animation: swirlAdded 1s;
  64. }
  65. .swirl-leave-active {
  66. animation: swirlAdded 1s reverse;
  67. }
  68. @keyframes swirlAdded {
  69. from {
  70. opacity: 0;
  71. rotate: 0;
  72. scale: 0.1;
  73. }
  74. to {
  75. opacity: 1;
  76. rotate: 360deg;
  77. scale: 1;
  78. }
  79. }
  80. #p1, #p2 {
  81. display: inline-block;
  82. padding: 10px;
  83. border: dashed black 1px;
  84. }
  85. #p2 {
  86. background-color: lightcoral;
  87. }
  88. </style>

JavaScript Transition 钩子

刚才提到的每个 Transition 类都对应一个事件,我们可以挂接该事件来运行一些 JavaScript 代码。

Transition 类JavaScript 事件
v-enter-frombefore-enter
v-enter-activeenter
v-enter-toafter-enter
enter-cancelled
v-leave-frombefore-leave
v-leave-activeleave
v-leave-toafter-leave
leave-cancelled (v-show only)

当在下面的实例中发生 after-enter 事件时,将运行一个显示红色 <div> 元素的方法。

实例

App.vue:

  1. <template>
  2. <h1>JavaScript Transition Hooks</h1>
  3. <p>This code hooks into "after-enter" so that after the initial animation is done, a method runs that displays a red div.</p>
  4. <button @click="pVisible=true">Create p-tag!</button><br>
  5. <Transition @after-enter="onAfterEnter">
  6. <p v-show="pVisible" id="p1">Hello World!</p>
  7. </Transition>
  8. <br>
  9. <div v-show="divVisible">This appears after the "enter-active" phase of the transition.</div>
  10. </template>
  11. <script>
  12. export default {
  13. data() {
  14. return {
  15. pVisible: false,
  16. divVisible: false
  17. }
  18. },
  19. methods: {
  20. onAfterEnter() {
  21. this.divVisible = true;
  22. }
  23. }
  24. }
  25. </script>
  26. <style scoped>
  27. .v-enter-active {
  28. animation: swirlAdded 1s;
  29. }
  30. @keyframes swirlAdded {
  31. from {
  32. opacity: 0;
  33. rotate: 0;
  34. scale: 0.1;
  35. }
  36. to {
  37. opacity: 1;
  38. rotate: 360deg;
  39. scale: 1;
  40. }
  41. }
  42. #p1, div {
  43. display: inline-block;
  44. padding: 10px;
  45. border: dashed black 1px;
  46. }
  47. #p1 {
  48. background-color: lightgreen;
  49. }
  50. div {
  51. background-color: lightcoral;
  52. }
  53. </style>

您可以使用以下实例中的 "Toggle" 按钮来中断 <p> 元素的 enter-cancelled 阶段,从而触发输入取消事件:

实例

App.vue:

  1. <template>
  2. <h1>The 'enter-cancelled' Event</h1>
  3. <p>Click the toggle button again before the enter animation is finished to trigger the 'enter-cancelled' event.</p>
  4. <button @click="pVisible=!pVisible">Toggle</button><br>
  5. <Transition @enter-cancelled="onEnterCancelled">
  6. <p v-if="pVisible" id="p1">Hello World!</p>
  7. </Transition>
  8. <br>
  9. <div v-if="divVisible">You interrupted the "enter-active" transition.</div>
  10. </template>
  11. <script>
  12. export default {
  13. data() {
  14. return {
  15. pVisible: false,
  16. divVisible: false
  17. }
  18. },
  19. methods: {
  20. onEnterCancelled() {
  21. this.divVisible = true;
  22. }
  23. }
  24. }
  25. </script>
  26. <style scoped>
  27. .v-enter-active {
  28. animation: swirlAdded 2s;
  29. }
  30. @keyframes swirlAdded {
  31. from {
  32. opacity: 0;
  33. rotate: 0;
  34. scale: 0.1;
  35. }
  36. to {
  37. opacity: 1;
  38. rotate: 720deg;
  39. scale: 1;
  40. }
  41. }
  42. #p1, div {
  43. display: inline-block;
  44. padding: 10px;
  45. border: dashed black 1px;
  46. }
  47. #p1 {
  48. background-color: lightgreen;
  49. }
  50. div {
  51. background-color: lightcoral;
  52. }
  53. </style>

显示 Prop

如果我们有一个元素要在页面加载时设置动画,我们需要在 <Transition> 组件上使用显示 Prop

  1. <Transition appear>
  2. ...
  3. </Transition>

在本例中,当页面首次加载时,显示 prop 将启动动画:

实例

App.vue:

  1. <template>
  2. <h1>The 'appear' Prop</h1>
  3. <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>
  4. <Transition appear>
  5. <p id="p1">Hello World!</p>
  6. </Transition>
  7. </template>
  8. <style>
  9. .v-enter-active {
  10. animation: swirlAdded 1s;
  11. }
  12. @keyframes swirlAdded {
  13. from {
  14. opacity: 0;
  15. rotate: 0;
  16. scale: 0.1;
  17. }
  18. to {
  19. opacity: 1;
  20. rotate: 360deg;
  21. scale: 1;
  22. }
  23. }
  24. #p1 {
  25. display: inline-block;
  26. padding: 10px;
  27. border: dashed black 1px;
  28. background-color: lightgreen;
  29. }
  30. </style>

元素间的转换

<Transition> 组件也可以用于在多个元素之间切换,只要我们使用 <v-if><v-else-if> 确保一次只显示一个元素:

实例

App.vue:

  1. <template>
  2. <h1>Transition Between Elements</h1>
  3. <p>Click the button to get a new image.</p>
  4. <p>The new image is added before the previous is removed. We will fix this in the next example with mode="out-in".</p>
  5. <button @click="newImg">Next image</button><br>
  6. <Transition>
  7. <img src="/img_pizza.svg" v-if="imgActive === 'pizza'">
  8. <img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
  9. <img src="/img_cake.svg" v-else-if="imgActive === 'cake'">
  10. <img src="/img_fish.svg" v-else-if="imgActive === 'fish'">
  11. <img src="/img_rice.svg" v-else-if="imgActive === 'rice'">
  12. </Transition>
  13. </template>
  14. <script>
  15. export default {
  16. data() {
  17. return {
  18. imgActive: 'pizza',
  19. imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],
  20. indexNbr: 0
  21. }
  22. },
  23. methods: {
  24. newImg() {
  25. this.indexNbr++;
  26. if(this.indexNbr >= this.imgs.length) {
  27. this.indexNbr = 0;
  28. }
  29. this.imgActive = this.imgs[this.indexNbr];
  30. }
  31. }
  32. }
  33. </script>
  34. <style>
  35. .v-enter-active {
  36. animation: swirlAdded 1s;
  37. }
  38. .v-leave-active {
  39. animation: swirlAdded 1s reverse;
  40. }
  41. @keyframes swirlAdded {
  42. from {
  43. opacity: 0;
  44. rotate: 0;
  45. scale: 0.1;
  46. }
  47. to {
  48. opacity: 1;
  49. rotate: 360deg;
  50. scale: 1;
  51. }
  52. }
  53. img {
  54. width: 100px;
  55. margin: 20px;
  56. }
  57. img:hover {
  58. cursor: pointer;
  59. }
  60. </style>

mode="out-in"

在上面的实例中,在删除前一个图像之前添加下一个图像。我们在 <Transition> 组件上使用 mode="out-in" propprop 值,以便在添加下一个元素之前完成元素的移除。

实例

除了 mode="out-in" 之外,此实例还使用计算值 'imgActive',而不是我们在上一实例中使用的 'newImg' 方法。

App.vue:

  1. <template>
  2. <h1>mode="out-in"</h1>
  3. <p>Click the button to get a new image.</p>
  4. <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>
  5. <button @click="indexNbr++">Next image</button><br>
  6. <Transition mode="out-in">
  7. <img src="/img_pizza.svg" v-if="imgActive === 'pizza'">
  8. <img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
  9. <img src="/img_cake.svg" v-else-if="imgActive === 'cake'">
  10. <img src="/img_fish.svg" v-else-if="imgActive === 'fish'">
  11. <img src="/img_rice.svg" v-else-if="imgActive === 'rice'">
  12. </Transition>
  13. </template>
  14. <script>
  15. export default {
  16. data() {
  17. return {
  18. imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],
  19. indexNbr: 0
  20. }
  21. },
  22. computed: {
  23. imgActive() {
  24. if(this.indexNbr >= this.imgs.length) {
  25. this.indexNbr = 0;
  26. }
  27. return this.imgs[this.indexNbr];
  28. }
  29. }
  30. }
  31. </script>
  32. <style>
  33. .v-enter-active {
  34. animation: swirlAdded 0.7s;
  35. }
  36. .v-leave-active {
  37. animation: swirlAdded 0.7s reverse;
  38. }
  39. @keyframes swirlAdded {
  40. from {
  41. opacity: 0;
  42. rotate: 0;
  43. scale: 0.1;
  44. }
  45. to {
  46. opacity: 1;
  47. rotate: 360deg;
  48. scale: 1;
  49. }
  50. }
  51. img {
  52. width: 100px;
  53. margin: 20px;
  54. }
  55. img:hover {
  56. cursor: pointer;
  57. }
  58. </style>

动态组件的转换

我们还可以使用 <Transition> 组件来设置动态组件之间切换的动画:

实例

App.vue:

  1. <template>
  2. <h1>Transition with Dynamic Components</h1>
  3. <p>The Transition component wraps around the dynamic component so that the switching can be animated.</p>
  4. <button @click="toggleValue = !toggleValue">Switch component</button>
  5. <Transition mode="out-in">
  6. <component :is="activeComp"></component>
  7. </Transition>
  8. </template>
  9. <script>
  10. export default {
  11. data () {
  12. return {
  13. toggleValue: true
  14. }
  15. },
  16. computed: {
  17. activeComp() {
  18. if(this.toggleValue) {
  19. return 'comp-one'
  20. }
  21. else {
  22. return 'comp-two'
  23. }
  24. }
  25. }
  26. }
  27. </script>
  28. <style>
  29. .v-enter-active {
  30. animation: slideIn 0.5s;
  31. }
  32. @keyframes slideIn {
  33. from {
  34. translate: -200px 0;
  35. opacity: 0;
  36. }
  37. to {
  38. translate: 0 0;
  39. opacity: 1;
  40. }
  41. }
  42. .v-leave-active {
  43. animation: slideOut 0.5s;
  44. }
  45. @keyframes slideOut {
  46. from {
  47. translate: 0 0;
  48. opacity: 1;
  49. }
  50. to {
  51. translate: 200px 0;
  52. opacity: 0;
  53. }
  54. }
  55. #app {
  56. width: 350px;
  57. margin: 10px;
  58. }
  59. #app > div {
  60. border: solid black 2px;
  61. padding: 10px;
  62. margin-top: 10px;
  63. }
  64. </style>