[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 - 进入/离开 & 列表过渡


<<:  iOS APP 开发 OC 第二十二天,Portocol

>>:  [DAY17]跟 Vue.js 认识的30天 - Vue 过渡(转场)及动画效果下篇(`<transition-group>`) - 多个元素的过渡及列表过渡

Rust-定义Closure(闭包)

一般来说Rust如果要排序数组会这样写 let mut arr = [10, 5, 9, 7, 6]...

Day28 firebase authentication

前两天我们教大家怎麽部属了网页,但网页的内容几乎没说。 基本上网页配置有右侧的bar及左侧的表格,这...

[Day 09] 剩下的时间规划

  由於感觉我这前几天的文章都字数以及内容都蛮少的,简单来说就只是想低空 飞过,打混过关;结果想透过...

Day5 用Scanner实作一支程序

在Day3时,我们有实做一支计算平行四边形的程序,但当时我们固定了平行四边形的底跟高,现在我们已经学...

物理访问控制系统(PACS)-重播攻击(Replay attack)

下图演示了针对生物识别系统的九个攻击点。从传感器到特徵提取器的生物特徵数据的回放是其中之一。 . “...