[重构倒数第14天] - Vue3处理动态效果(二)

前言

该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系列文章,在这边整理了许多我自己使用Vue重构很多网站的经验分享给读者们。

上一个章节我们讲了两个在 Vue 里面做动态的方式,今天来说另外两个做法。

第三种:使用第三方动画工具 (GSAP)

GSAP 是一个功能强大,且 API 简单,可以使用在一般的网页或是canvas上面的动画函式库,因为它是为了专门做动画所诞生的函式库,所以不管是效能还是它所提供的功能,原比 JQuery 的 animate来的更加强大。

基本范例一 : 把一个DOM从左边移动到右边

<div class="box-100 bg-red"></div>
.box-100{
    width: 100px;
    height: 100px;
}
.bg-red{
    background-color: red;
}
gsap.to(".box-100", {duration: 1, x: 200, ease: "elastic"});
  • .to: 动画是从起点到终点的执行
  • duration : 这个动画执行的秒数
  • x: 水平移动,绑定的元件最後到达的地方
  • ease : 动画速率

mike vue gsap

codepen范例 : https://codepen.io/MikeCheng1208/pen/ExXgQYg

基本范例二 : 依序执行动画

<div class="cube1 box-100 bg-red"></div>
<div class="cube2 box-100 bg-blue"></div>
<button id="btn">click</button>
gsap.timeline({ defaults: { duration: 1, ease:"elastic" }})
    .to(".cube1", {x: 100})
    .to(".cube2", {y: 100})
    .to(".cube2", {x: 100, duration: 0,3}) 
  • .timeline : 控制动画执行的时间线,可以产生补间影格让动画依序执行。

先把动画参数设定完成後,在执行.to,之後所有的 .to 会全部参照 timeline 设定好的去执行,当然你也可以中途去改变,最後的方块执行速度会变快,不会照 timeline 设定的 defaults 去执行。

mike vue gsap

codepen 范例 : https://codepen.io/MikeCheng1208/pen/jOwMZNj

接下来我们来看怎麽在 Vue 里面使用 GSAP 吧 !

这是我们要使用GSAP来制作的效果,利用卷轴的卷动,来让我的图片依序照不同的方向跟旋转出现。

mike vue gsap

我们先来看 template 的部分,就是把图片给放到上面来

<template>
<div id="box">
     <div class="pic pic1"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic2"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic3"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic4"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic5"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic6"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic7"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic8"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic9"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic10"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
     <div class="pic pic11"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
</div>
</template>

然後css的部分

<style>
#box{
    width: 240px;
    height: auto;
    margin: 0 auto;
    padding-top: 250px;
}
.pic{
    width: 240px;
    height: auto;
    overflow: hidden;
    background-color: #fff;
    margin-bottom: 20px;
    opacity: 0;
}
.pic > img{
    display: block;
    width: 240px;
    height: auto;
}
.pic1{
    margin-left: -100px;
}
.pic2{
    margin-left: 100px;
}
.pic3{
    margin-top: 100px;
}
.pic4{
    transform: scale(0);
}
.pic5{
    width: 0;
}
.pic6{
    transform:scaleX(-1.4);
}
.pic7{
    margin-left: -100px;
}
.pic8{
    margin-left: 100px;
}
.pic9{
    transform:scaleY(-1.4);
}
.pic10{
    margin-top: 100px;
}
.pic11{
    width: 0;
}
</style>

在这边我把所有图片的透明度全部设定成 0,然後每一张图片依序的起始位置就是我动画要出现的地方,你会看到有 transform: scale(0); 或是 margin-left: -100px;之类的,所以接下来就是要透过 GSAP 来把它恢复原状,中间的过程做补间动画

<script>
import { onMounted, onUnmounted } from 'vue';
import gsap from 'gsap';
export default {
  setup(){
      
      let tl = null;
      
      // 抓 document 的 高
      const getDocumentHeight = () => {
        const body = document.body;
        const html = document.documentElement;
        return Math.max(
          body.offsetHeight,
          body.scrollHeight,
          html.clientHeight,
          html.offsetHeight,
          html.scrollHeight
        )
      }
    
      // 抓卷轴的位置
      const documentTop = () => (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop
      
      // 动画初始化
      const gsapInit = () => {
        tl = gsap.timeline({defaults: { duration: 1 }});
        tl.to(".pic1", {"margin-left": 0, "opacity":1});
        tl.to(".pic2", {"margin-left": 0, "opacity":1});
        tl.to(".pic3", {"margin-top": 0, "opacity":1});
        tl.to(".pic4", {"transform": "scale(1)", "opacity":1, ease: Bounce.easeOut});
        tl.to(".pic5", {"width": "240px", "opacity":1});
        tl.to(".pic6", {"transform":"scaleX(1)", "opacity":1});
        tl.to(".pic7", {"margin-left": 0, "opacity":1});
        tl.to(".pic8", {"margin-left": 0, "opacity":1});
        tl.to(".pic9", {"transform":"scaleY(1)","opacity":1});
        tl.to(".pic10", {"margin-top": 0, "opacity":1});
        tl.to(".pic11", {"width": "240px", "opacity":1});
        tl.pause();
      }
    
      const handleWinScroll = () =>{
          const scrollTop = documentTop();
          const docHeight = getDocumentHeight();
          const winHeight = window.innerHeight;
          const scrollPercent = (scrollTop) / (docHeight - winHeight);
          tl.progress(scrollPercent);
      }
    
      onMounted(()=> {
          gsapInit();
          window.addEventListener("scroll", handleWinScroll);
      })
      
      onUnmounted(()=> {
          window.removeEventListener("scroll" ,handleWinScroll);
      })
    
      return {}
  }
};
</script>

这边要注意我宣告了一个 tl但是我并没有用 refreactive 包起来,这是因为这个是要给 gsap 的实体存放的,不需要透过 Vue 包起来,所以不需要用 refreactive

tl = gsap.timeline({defaults: { duration: 1 }});
tl.to(".pic1", {"margin-left": 0, "opacity":1});
tl.to(".pic2", {"margin-left": 0, "opacity":1});
tl.to(".pic3", {"margin-top": 0, "opacity":1});
tl.to(".pic4", {"transform": "scale(1)", "opacity":1, ease: Bounce.easeOut});
tl.to(".pic5", {"width": "240px", "opacity":1});
tl.to(".pic6", {"transform":"scaleX(1)", "opacity":1});
tl.to(".pic7", {"margin-left": 0, "opacity":1});
tl.to(".pic8", {"margin-left": 0, "opacity":1});
tl.to(".pic9", {"transform":"scaleY(1)","opacity":1});
tl.to(".pic10", {"margin-top": 0, "opacity":1});
tl.to(".pic11", {"width": "240px", "opacity":1});
tl.pause();

这边你会看到我依序地把图片一个一个恢复原状的位置,所以一开始的时候我会用 CSS 把元件的位置给移开,再透过GSAP给补回来。

这边要特别注意一下 tl.pause();这个函式,它是把这个动画整个先暂停下来,然後在使用 scroll事件来跑我们动画的补间进度,透过换算,我们可以得出一个卷轴从最上面滑到最下面的百分比,然後把这个百分比塞入 tl.progress();这个函式内,就可以依照卷轴百分比来显示你的图片。

  • tl.progress() : 是百分比是从 0 ~ 1 ;

透过 GSAP 的方式我们可以很简单的完成这样子的卷轴控制物件出来的动态,只是要注意,抓取 DOM 的时候需要等 DOM 都 render 到画面上,所以必须要在 onMounted 的时候再去执行,我也是在 onMounted才去执行 gsapInit 这个函式的。

codepen范例 : https://codepen.io/MikeCheng1208/pen/JjJRpXJ

GSAP补充教学

我有录制了一份 GSAP3 的免费教学放在 Youtube 上面,如果对 GSAP 有兴趣的可以看一下,或是上 GSAP 的官网看看。

GSAP 官网 : https://greensock.com/gsap/
youtube 连结 : https://www.youtube.com/playlist?list=PLbOfcOk7bN40WfzgRMLEQzkayS8pjWO_e

第四种:Vue 的 transition + 第三方动画工具 (GSAP)

在上一篇教学中,有提到 transition 有提供动画的 Lifecycle Hooks,所以我们可以针对每个动画所执行的阶段去做一些操作,不过同样的我们也可以在这些 Lifecycle Hooks 里面去做动画的操作,舍弃掉原本的 CSS,透过第三方动画工具整合,我们可以做出更多 CSS 难以做到的动画效果,这边我一样选用 GSAP 来做整合,GSAP 可是连 Vue 都指令拿来做文件的范例用的,你说这可有多好用呢~

我们先来看一下 transition 的 Lifecycle Hooks。

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
  :css="false"
>
</transition>

进场

  • @before-enter:开始的动画开始前。

  • @enter:动画开始的执行过程。

  • @after-enter:开始的动画结束时。

  • @enter-cancelled:开始的动画被取消。

离开

  • @before-leave:离开的动画开始前。
  • @leave:离开动画的执行过程。
  • @after-leave:离开动画的结束时。
  • @leave-cancelled:离开的动画被取消。

额外设置

  • v-bind:css:设定 false 可以让动画执行的过程中不会受到 css的影响。

关於 Transition 的 Lifecycle Hooks 官方文件 : https://v3.vuejs.org/guide/transitions-enterleave.html#javascript-hooks

其实大部分的 Lifecycle Hooks 就跟我们的 css class 是一样的,只是多了一两个东西而已,所以接下来我们就要来看怎麽把 Vue 跟 GSAP 做一个整合。

vue mike gsap

我们拿上一个章节所用的这个轮播范例来改,我们拿掉了 name,加上了 Hooks 。

<transition-group
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
    :css="false"
>
   <img 
        v-for="(item, idx) in slidList" 
        v-show="imgIdx === idx" 
        :key="item.id" 
        :src="item.src"
   />
</transition-group>
// --------
// ENTERING
// --------
const beforeEnter = (el) =>  {
    gsap.set(el, {
        scaleX: 0,
        scaleY: 0,
        x: 0,
        y: 0,
        opacity: 0,
    })
}
const  enter = (el, done) =>  {
    gsap.to(el, {
        duration: 1,
        scaleX: 1,
        scaleY: 1,
        opacity: 1,
        ease: 'elastic.inOut(1,2, 1)',
        onComplete: done
    })
}

// --------
// LEAVING
// --------
const leave = (el, done) => {
    gsap.to(el, {
        duration: 0.7,
        scaleX: 3,
        scaleY: 0.05,
        x: 0,
        opacity: 0,
        ease: 'elastic.inOut(2.5, 1)'
    })
    gsap.to(el, {
        duration: 0,
        delay: 0.5,
        onComplete: done
    })
}

Lifecycle Hooks 使用的时候会回传当前执行动画的 DOM 实体,所以我们就要拿这个实体直接来套入 GSAP 中,就不需要直接写 class 的名称在里面。

首先我们透过 beforeEnter 先用gsap.set来设定动画开始以前的样式,然後当动画在执行的时候在改变回来,然後当动画执行到离开(leave) 的时候,再去做离开的动画样式处理,只是要特别注意,在 enterleave hooks 中,必须要在动画结束後调用 done这个函式,不然的话当你动画还没有执行完你在执行下一段的时候,补间就会直接完成动画,没有中间的动画效果,所以 GSAP 在动画完成之後有提供自己的 lifecycle,动画完成之後执行的 lifecycle 叫 onComplete,所以我就可以把, enterleave 回传的done函式放入 onComplete 去执行它。

codepen 范例 : https://codepen.io/MikeCheng1208/pen/OJgRQBK

先告一个段落

关於 Vue 的动画上面的处理就先告一个段落,其实网路上面也是还有其他的套件或是整合方式,只是在工作上面来说,这四种动画的处理方式已经可以解决大部分的问题了,我都有附上 codepen 的范例,有兴趣的朋友可以自己玩玩看。

Mike vue

那如果对於Vue3不够熟的话呢?

Ps. 购买的时候请登入或注册该平台的会员,然後再使用下面连结进入网站点击「立即购课」,这样才可以让我获得更多的课程分润,还可以帮助我完成更多丰富的内容给各位。

我有开设了一堂专门针对Vue3从零开始教学的课程,如果你觉得不错的话,可以购买我课程来学习
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果对於JS基础不熟的朋友,我也有开设JS的入门课程,可以参考这个课程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

订阅Mike的频道享受精彩的教学与分享

Mike 的 Youtube 频道
Mike的medium
MIke 的官方 line 帐号,好友搜寻 @mike_cheng


<<:  虹语岚访仲夏夜-3(专业的小四篇)

>>:  Ruby on Rails Model 基本操作之 CRUD CRUD 之 C(Create)

【Day04-档案】你知道Excel最大可以开多少笔资料吗?

前一天我们介绍了用来资料处理最基本的pandas套件 那今天我们则是来谈一下不同的档案类型 我们都知...

Day 24: Behavioral patterns - Observer

目的 当需要多个物件同时「监听」一个相同物件,一旦该物件发生变化时,监听的物件们将自动采取相对应的处...

【从零开始的Swift开发心路历程-Day3】建立第一个project!

昨天大概熟悉了一下Swift的语法後,我们今天就来正式开工啦! 首先我们打开Xcode,选择Crea...

Day18. 一起动手做弹珠台!(4)

今天要来运用昨天我们前两天学到的滑鼠互动方式来为我们的弹珠台加上互动操作。 在弹珠台里,球碰到钉子就...

WordPress WPS Hide Login 外挂教学,隐藏登入网址,防止暴力登入攻击

这几天为提升 WordPress 站台安全性,安装了 WordPress 防火墙及防毒外挂 Word...