iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

0
自我挑戰組

跟 VueJS 認識的30天系列 第 16

[DAY16]跟 Vue.js 認識的30天 - Vue 過渡(轉場)及動畫效果上篇(`<transition>`)

https://ithelp.ithome.com.tw/upload/images/20210128/20127553XIGFbo0Gw1.png

先看上圖來了解 Vue 將動畫進入跟離開這 2 種階段,而在進入跟離開階段又分別配置了 3 種狀態 — 初始狀態(進入前、離開前)、進行中狀態、結束狀態(進入後、離開後)。

使用 CSS 操作過渡(轉場)及動畫效果

使用 CSS 操作過渡效果

https://ithelp.ithome.com.tw/upload/images/20210128/201275534iACjQC2zs.png

Vue 文件是如何製作出過渡(轉場)及動畫效果,就是透過加入不同的 class 屬性來進行的。

在 Vue 文件中已經有詳細的說明這些 class 屬性是在那些時候出現及消失的,所以這邊就不細說,來看看實際上的變化。

從無到有

https://ithelp.ithome.com.tw/upload/images/20210128/20127553l6QmmSVRh9.png

https://ithelp.ithome.com.tw/upload/images/20210128/20127553QiQEZkAQid.png

https://ithelp.ithome.com.tw/upload/images/20210128/20127553NsyCXggiHM.png

可以發現 .v-enter 並沒有出現在元素上,而僅僅是在 CSS 中定義該元素的初始值。 .v-enter-active.v-enter-to 會在進行轉場效果時一起出現,並在轉場效果結束時一起消失。確定轉場效果結束後所有的 v-* 都會消失。

從有到無

https://ithelp.ithome.com.tw/upload/images/20210128/20127553hgBMrMy66Y.png

https://ithelp.ithome.com.tw/upload/images/20210128/20127553loJBaq5hJk.png

https://ithelp.ithome.com.tw/upload/images/20210128/20127553aVuS05QNOW.png

使用命名 <transition name="transitionName"> 來取代前綴詞 v

當我們使用未命名的 <transition> 時,是透過 CSS 操作 .v-* 來進行轉場或動畫效果的,如下:

<button @click="show=!show">切換</button>
<transition>
  <p v-show="show">我是要進行過渡的元素</p>
</transition>

<style>
/*要加入CSS屬性才會有過渡效果*/
/*初始狀態(進入前)*/
/*正常來說,進入前的狀態會跟離開後相同*/
.v-enter,.v-leave-to{
  opacity:0;
}
/*轉場進行中*/
/*轉場效果要寫在這*/
.v-enter-active,.v-leave-active{
  transition: all 5s;
}
/*結束狀態(進入後)*/
/*正常來說,進入後的狀態會跟離開前相同*/
.v-enter-to,.v-leave{
  opacity:1;
}
</style>

<script>
const vm = new Vue({
  el: "#vm",
  data:{
    show:false
  }
});
</script>

但當我們有許多轉場或動畫時,就會很不方便,這時就可以使用命名 <transition name="transitionName"> 來解決,而且命名 <transition name="transitionName"> 也比較方便管理,此時的 transitionName 就會取代前綴詞 v ,例如 .v-enter.transitionName-enter.v-enter-active.transitionName-enter-active ,以此類推。

<button @click="show=!show">切換</button>
<transition name="fade">
  <p v-show="show">我是要進行過渡的元素</p>
</transition>

<style>
/*要加入CSS屬性才會有過渡效果*/
/*初始狀態(進入前)*/
/*正常來說,進入前的狀態會跟離開後相同*/
.fade-enter,.fade-leave-to{
  opacity:0;
}
/*轉場進行中*/
/*轉場效果要寫在這*/
.fade-enter-active,.fade-leave-active{
  transition: all 5s;
}
/*結束狀態(進入後)*/
/*正常來說,進入後的狀態會跟離開前相同*/
.fade-enter-to,.fade-leave{
  opacity:1;
}
</style>

<script>
const vm = new Vue({
  el: "#vm",
  data:{
    show:false
  }
});
</script>

使用 CSS 操作動畫效果

其實使用 <transition> 來操作動畫效果的方法跟操作轉場效果的方法相同,唯一的不同點是操作動畫效果的時候不需要再設定初始跟結束狀態了,因為這些狀態在 CSS 動畫效果中已經設定好了。

<button @click="scale=!scale" class="btn">切換</button>
<transition name="scale">
  <p v-show="scale">我是要進行動畫的元素</p>
</transition>

<style>
.scale-enter-active {
  animation: bounce-in 5.5s;
}
.scale-leave-active {
  animation: bounce-in 5.5s reverse;
}
@keyframes bounce-in {
  /*動畫的開始跟結束狀態已經在這裡指定了,所以可以不用再多寫初始狀態跟結識狀態(*-enter、*enter-to、*-leave、*leave-to)*/
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
</style>

<script>
const vm = new Vue({
  el: "#vm",
  data:{
    scale:false
  }
});
</script>

結合第三方動畫庫來實現動畫效果

在了解如何使用第三方動畫庫之前,我們必須先了解使用的規則,在這裡我們使用 Animate.css 來作為第三方動畫庫來進行講解。

  • 必須先明白要使用 Animate.css 時,要先載入該動畫庫。

  • 透過 class 屬性來使用 Animate.css 時,必須注意一定要加入 animate__animatedanimate__動畫效果名

    <!-- animate__animated 代表使用 Animate.css -->
    <!-- animate__bounce 代表使用 bounce 這個動畫效果 -->
    <!-- animate__delay-2s 代表使用這個動畫效果延遲的時間 -->
    <h1 class="animate__animated animate__bounce animate__delay-2s">An animated element</h1>
    
  • 透過 @keyframes 來使用 Animate.css 時,在該元素的 CSS 裡直接指定 Animate.css 的動畫名。

h1 {
 animation: bounce; /* referring directly to the animation's @keyframe declaration */
 animation-duration: 2s; /* don't forget to set a duration! */
 }

上面都是 Animate.css 給出的使用方法,想了解更多可以參考一下 Animate.css 文件,但要怎麼結合到 Vue 裡的 <transition> 呢?來分別講解一下 Animate.css 提供的 2 種不同的使用方法跟 Vue 的結合。

利用 @keyframes

這種方法也就是之前說的,利用 Vue 裡的 <transition> 會自動產生預設的 className 的特性(.v-* 或是 .transitionName-*),然後結合 Animate.css 的中 @keyframes 使用方法來做動畫的。

這個跟上面的[使用 CSS 操作動畫效果](#使用 CSS 操作動畫效果)是相同的。

<button @click="show=!show">切換</button>
<transition name="bounce">
  <p v-show="show">我是要進行過渡的元素</p>
</transition>

<style>
/*在 transition 自動產生的 .bounce-enter-active 裡,並在元素的 CSS 中加入Animate.css 中的動畫名*/
.bounce-enter-active{
  animation:bounceIn;
  animation-duration: 2s;
}
/*在 transition 自動產生的 .bounce-leave-active 裡,並在元素的 CSS 中加入Animate.css 中的動畫名*/
.bounce-leave-active{
  animation:bounceOut;
  animation-duration: 2s;
}
</style>
<script>
const vm = new Vue({
  el: "#vm",
  data:{
    show:false
  }
});
</script>

利用CSS

在 Vue 文件中有提到可以透過幾個屬性來更改 <transition> 預設的 class 屬性值( .v-* 或是 .transitionName-* )。

https://ithelp.ithome.com.tw/upload/images/20210128/20127553aLzaecXA0n.png

所以可以利用這個方法讓元素在進行到 enter-active 這個階段時,讓自定義轉場 class 名取代 .v-* 或是 .transitionName-*

<button @click="show=!show">切換</button>
<transition enter-active-class="animate__tada" leave-active-class="animate__bounceOut ">
  <p v-show="show" class="animate__animated animate__slower">我是要進行過渡的元素</p>
</transition>

<script>
const vm = new Vue({
  el: "#vm",
  data:{
    show:false
  }
});
</script>

在上面因為 .animate__animated.animate__slower 是不影響動畫效果的,並且也是進入及離開動畫都會用到的相同 class ,考慮到 Vue 模組會將模組標籤的 class 跟模組內部的根元素的 class 組合在一起的特性(可以參考DAY12 | 跟 Vue.js 認識的30天 - Vue 模組資料傳遞(props)),所以將這些重複的 class 統一放在內部。

使用 :duration 設定拔掉轉場或動畫樣式的時間

剛開始以為 :duration 是指轉場或動畫樣式的持續時間,經過操作才發現是完成轉場或動畫後,轉場或動畫樣式的 class 被拔除的時間。

動態綁定 :duration 的值可以為數值或物件,利用數值即進入跟離開的樣式的 class 被拔除時間相同,而物件可以分開指定 class 被拔除的時間,如 :duration="{ enter: 500, leave: 800 }"

<button @click="flash=!flash">切換</button>
<!--要注意這個 duration 指定的是 class被拔掉的時間-->
<!-- 也可以利用 :duration="{ enter: 500, leave: 800 }" 指定不同的時間-->
<transition name="flash" :duration="5000">
  <p v-show="flash">我是要進行過渡的元素</p>
</transition>

<style>
.flash-enter-active,
.flash-leave-active {
  animation: flash 1s;
}
@keyframes flash {
  0%,
  10%,
  20%,
  30%,
  35%,
  40%,
  50%,
  70%,
  85%,
  100% {
    opacity: 0;
  }
  5%,
  15%,
  25%,
  38%,
  45%,
  60%,
  80%,
  95% {
    opacity: 1;
  }
}
</style>

使用 JavaScript Hook 操作轉場及動畫效果

使用 JavaScript Hook 操作轉場及動畫效果,是利用事件監聽的方式( v-on )來完成在動畫的每個階段可以處理的事件。

但是必須注意到一點,在使用 <transition> 時, Vue 會產生相應的 class ,如 .v-* 或是 .transitionName-* ,但如果我們是要直接使用 JavaScript Hook 來操作轉場或動畫效果時,將導致某些 class 會一直存在(如下圖)。

https://ithelp.ithome.com.tw/upload/images/20210128/20127553jNpPA4i6Zq.png

要解決這個問題很簡單就是在 <transition> 加上 :css="false" 來避免產生預設 class 。

<transition :css="false"></transition>

另外在條件渲染的方面,建議使用 v-if ,這是因為 v-if 是會將該元素或模組銷毀或重建,而 v-show 在使用 JavaScript Hook 操作 <transition> 時,會導致在不同事件發生時,上一個事件的樣式不會消失。

(使用 Velocity)

<button @click="jsHook=!jsHook">切換</button>
<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:leave="leave"
  v-bind:css="false"
>
  <p v-if="jsHook">使用JavaScript Hook完成動畫</p>
</transition>
<script>
const vm = new Vue({
  el: "#vm",
  data: {
    jsHook: false
  },
 methods: {
    beforeEnter(el) {
      // 進入動畫前的初始值
      console.log("beforeEnter");
      el.style.opacity = 0;
      el.style.transformOrigin = "left";
    },
    enter(el, done) {
      // 進入動畫及欲達成的動畫樣式
      console.log("enter");
      Velocity(el, { opacity: 1, fontSize: "1.4em" }, { duration: 1300 });
      // 取消 , { complete: done } 再看看console
      Velocity(el, { fontSize: "1em" }, { complete: done });
    },
    afterEnter(el) {
      // enter(el, done) 中的回調函數 done() 即為afterEnter(el)
      // 所以必須要在 enter() 中使用 done() 才會調用 afterEnter(el)
      console.log("afterEnter");
    },
    enterCancelled(el){
      // 在 enter(el, done) 中未使用回調函數 done()的話,在執行 beforeLeave(el) 前就會先執行 enterCancelled(el)
      console.log("enterCancelled");
    }
    ,
    beforeLeave(el) {
      // 離開動畫前的初始值
      console.log("beforeLeave");
    },
    leave(el, done) {
      // 離開動畫及離開後欲達成的動畫樣式
      console.log("leave");
      Velocity(
        el,
        { translateX: "15px", rotateZ: "50deg" },
        { duration: 1600 }
      );
      Velocity(el, { rotateZ: "100deg" }, { loop: 2 });
      // 執行 done() 會讓 `v-if` 直接銷毀 DOM
      Velocity(
        el,
        {
          rotateZ: "45deg",
          translateY: "30px",
          translateX: "30px",
          opacity: 0
        },
        { complete: done }
      );      
    },
    afterLeave(el) {
      // leave(el, done) 中的回調函數 done() 即為afterLeave(el)
      // 所以必須要在 leave(el, done) 中使用 done() 才會調用即為 afterLeave(el)
      console.log("afterLeave");
    }
  }
});
</script>

初始畫面的動畫渲染(畫面出現即產生動畫)

<transition> 標籤中加入屬性 appear ,來讓 Vue 知道這一個動畫(轉場)模組是要在初始畫面出現時就要產生動畫的。所以當初使畫面出現時,就直接套用 .transitionName-enter.transitionName-enter-active.transitionName-enter-to 等 class 效果。

<button @click="appear=!appear">切換</button>
<transition name="appear" appear>
  <p v-show="appear">我是要進行過渡的元素</p>
</transition>
<style>
.appear-enter,
.appear-leave-to {
  opacity: 0;
}

.appear-enter-active,
.appear-leave-active {
  transition: all 10s;
}

.appear-enter-to,
.appear-leave {
  opacity: 1;
}
</style>

但如果是想要初始畫面的動畫效果是特殊的,那就可以利用自定義 class 來將該初始畫面的動畫效果特別化。

初始畫面的動畫的自定義 class :

  • appear-class="customAppearName"

  • appear-active-class"customAppearActiveName"

  • appear-to-class"customAppearToName"

需要特別注意一點 appear 只針對初始畫面,該元素是存在時才會有動畫效果,所以在初始畫面中該元素為 v-show="false"v-if="false" 時,加上 appear 並不會有任何效果,也因此 appear 是只有使用進入動畫的 class 。

<button @click="appear=!appear">切換</button>
<transition name="appear" appear appear-class="custom-appear" 
appear-active-class="custom-appear-active" appear-to-class="custom-appear-to">
  <p v-show="appear">我是要進行過渡的元素</p>
</transition>
<style>
.custom-appear {
  opacity: 0;
}

.custom-appear-active {
  transition: all 10s;
}

.custom-appear-to {
  opacity: 1;
}
</style>

appear 效果也能通過 JavaScript Hook 來達成,但如同上面所說的 appear 只針對初始畫面,該元素是存在時才會有動畫效果,所以在使用時也只有進入動畫的 JavaScript Hook 。

<button @click="appear=!appear">切換</button>
<transition appear
  v-on:before-appear="beforeAppear"
  v-on:appear="appear"
  v-on:after-appear="afterAppear"
  v-on:appear-cancelled="appearCancelled"
  v-bind:css="false"
>
  <p v-if="jsHook">使用JavaScript Hook完成動畫</p>
</transition>
<script>
const vm = new Vue({
  el: "#vm",
  data: {
    jsHook: false
  },
 methods: {
    beforeAppear(el) {
      // 進入動畫前的初始值
      console.log("beforeEnter");
      el.style.opacity = 0;
      el.style.transformOrigin = "left";
    },
    appear(el, done) {
      // 進入動畫及欲達成的動畫樣式
      console.log("enter");
      Velocity(el, { opacity: 1, fontSize: "1.4em" }, { duration: 1300 });
      // 取消 , { complete: done } 再看看console
      Velocity(el, { fontSize: "1em" }, { complete: done });
    },
    afterAppear(el) {
      // enter(el, done) 中的回調函數 done() 即為afterEnter(el)
      // 所以必須要在 enter() 中使用 done() 才會調用 afterEnter(el)
      console.log("afterEnter");
    },
    appearCancelled(el){
      // 在 enter(el, done) 中未使用回調函數 done()的話,在執行 beforeLeave(el) 前就會先執行 enterCancelled(el)
      console.log("enterCancelled");
    }
  }
});
</script>

因為 Vue.js - 进入/离开 & 列表过渡 實在太多了,所以剩下的就留到下一篇記錄了。

Demo:DAY16 | 跟 Vue.js 認識的30天 - Vue 過渡及動畫效果

參考資料:

Vue.js - 进入/离开 & 列表过渡


上一篇
[DAY15]跟 Vue.js 認識的30天 - Vue 動態模組(Dynamic Components)
下一篇
[DAY17]跟 Vue.js 認識的30天 - Vue 過渡(轉場)及動畫效果下篇(`<transition-group>`) - 多個元素的過渡及列表過渡
系列文
跟 VueJS 認識的30天21

尚未有邦友留言

立即登入留言