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

前言

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

我们在开发网站的时候很常会处理 UI 的动态效果,以前大家会很常使用 JQuery 的 animate 的函式去做动态效果,但是 JQuery 的 animate 效能不是这麽好,再加上它只能使用在DOM上面,多少觉得有些不方便,所以今天要来介绍四种我自己在Vue上面开发网页动态上面很常使用的方式。

第一种:CSS3 制作动态

在制作网页的时候有许多小地方的动态其实可以不用透过JS的方式来处理,透过 CSS3 的 transition 还有 @keyframes 可以很轻易地达成,也好去把我们动画的逻辑还有程序的业务逻辑给分开,我们来看一下这两个的范例。

1. CSS3 的 transition + class Toggle

Vue Mike

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

<script>
import { ref } from 'vue'
export default {
  setup(){
    const isOpen = ref(false);
    
    const handleMenuOpen = (bool) => {
      isOpen.value = bool
    }
    
    return {
      isOpen,
      handleMenuOpen
    }
  }
};
</script>

<template>
	<!-- 开启选单的按钮 -->
    <a class="menuBtn" @click="handleMenuOpen(true)">
      <i class="fas fa-bars fa-3x"></i>
    </a>

    <div class="content"></div>

	<!-- 选单 -->
    <div :class="['menu', {open: isOpen}]">
        
	    <!-- 关闭选单的按钮 -->
        <a class="closeBtn" @click="handleMenuOpen(false)">
          <i class="fas fa-times fa-3x"></i>
        </a>
        
        <ul class="nav">
            <li><a>abous</a></li>
            <li><a>content</a></li>
            <li><a>user</a></li>
            <li><a>address</a></li>
        </ul>
    </div>

</template>

<style lang="scss">
#app{
    // 以上省略...
    
    .menu{
        position: fixed;
        top: 0;
        right: -350px;
        width: 350px;
        height: 100%;
        z-index: 20;
        background-color: #fff;
		
        // 对 right 属性添加 transition 动画
        transition: right 0.3s;
        &.open{
          right: 0px;
        }
        
    	// 以下省略...
    }
}
</style>

我们在这边使用了 css3 的 transition 属性,因为我们的选单是用定位的方式去摆放位置,所以针对 right这个属性去处理,只要 right 一变动 transition 就会自动把改变的 value 中间给做上补间动画。

关於 transition 的细节我们可以参考 MDN https://developer.mozilla.org/zh-TW/docs/Web/CSS/transition

不过要特别注意一件事情,很多人会为了贪图方便,所以这样写

.menu{
    // 对全部的属性添加 transition 动画
    transition: all 0.3s;
    &.open{
        right: 0px;
    }
}

all的方式可以对 .menu全部的属性都给予动画,但是这样不是很好,因为我们并非全部都要使用 transition,为了可以一眼就看出哪个属性被赋予的动画,也可以避免无谓的效能浪费,建议还是乖乖写上单一属性就好,如果要写多个属性的话可以这样,用 , 的方式串起来。

transition: right 0.3s, background 0.3s, color 0.3s;

2. CSS3 的 @keyframes + class Toggle

Vue3 mike

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

<script>
import { ref } from 'vue'
export default {
  setup(){
    const isError = ref(false);
    
    const handleAlert = (bool) => {
      isError.value = bool;
    }
    
    return {
      isError,
      handleAlert
    }
  }
};
</script>

<template>
    <div class="content">
      
      <button @click="handleAlert(true)">开启Alert</button>
      
      <div :class="['alert', {open: isError}]">
          <i class="fas fa-times fa-6x" @click="handleAlert(false)"></i>
          <h1>发生未知的错误</h1>
      </div>
    </div>
</template>

<style lang="scss">
@keyframes showAnim {
  from {
    bottom: -90%;
  }
  70% {
    bottom: 70%;
  }
  to {
    bottom: 50%;
  }
}
#app{
    // 以下省略 ...
    .content{
      // 以下省略 ...
      .alert{
          position: absolute;
          bottom: -50%;
          left: 50%;
          transform: translateX(-50%) translateY(50%);
          width: 500px;
          height: 400px;
          border-radius: 16px;
          background-color: #fff;
          display: flex;
          justify-content: center;
          align-items: center;
          flex-direction: column;
          box-shadow: 0 0 30px rgba(#000, 0.5);
          &.open {
              animation-name: showAnim;
              animation-duration: 0.45s;
              animation-iteration-count: 1;
              animation-fill-mode: forwards;
              animation-timing-function: ease-in-out;
          }
      }
    }
}
</style>

关於 @keyframes 的细节我们可以参考 MDN https://developer.mozilla.org/zh-CN/docs/Web/CSS/@keyframes

这边我们一样是用切换 class 的方式来控制,只是这次的动画需要更加的处理细节,你会看到我的 demo 有一个弹性的效果,像这样的细节就可以使用 @keyframes 的方式来处理,再搭配 animation 属性来处理,就可以很轻易的处理大部分网页上的动态。

我们现在已经学会了使用 CSS3 来取代以前我们使用 JQuery 的 animate 的动态效果,透过 CSS3 的动画,我们可以获得更顺畅的体验,更好的管理动画的逻辑。

第二种:Vue 的 transition 动画

除了透过切换 class 加入动画以外,Vue 提供了一个 transition 的 component,让我们可以用简单的方式可以处理 component 之间的过渡动画,我们只需要把你要执行动画的 component 给包起来,透过 v-if 或是 v-show 就可以使用了。

  <transition>
      <div v-if="isShow"></div>
  </transition>

以下就是我们用 transition 完成之後的样子

Vue3_transition mike

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

<script>
import { ref } from 'vue'
export default {
  setup(){
    const isShow = ref(false);
    
    const handleAlert = (bool) => {
      isShow.value = bool;
    }
    
    return {
      isShow,
      handleAlert
    }
  }
};
</script>

<template>
    <div class="content">
      <button @click="handleAlert(true)">开启Alert</button>
      
      <transition name="fade">
          <div class="alert" v-if="isShow">
              <i class="fas fa-times fa-6x" @click="handleAlert(false)"></i>
              <h1>发生未知的错误</h1>
          </div>
      </transition>
        
    </div>
</template>

<style lang="scss">
    
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
    
// 以下省略...
</style>

你会看到我在 transition 身上给了一个 name 叫做 fade,这个时候我就可以定义我的 transition 动画在执行的时候它的 class 的定义,透过 v-if 或是 v-show ,来达到过渡的动态效果。

<transition name="mike"></transition>
// 这些 class 就是看你的 transition 的 name 叫什麽,前面的名称就是你的 name
.mike-enter-active, .mike-leave-active {}
.mike-enter-from, .mike-leave-to {}

我们来仔细探讨 transition 在执行的的 6 个状态,首先来看一下官网的这张图。

vue3 transitions mike

进场离开 的过程中,会有 6 个 class 的状态 ( 详情请看官方文件 ),Vue2 跟 Vue3 的状态名称有稍微不一样,所以看文件不要看错。

进场

  1. v-enter-from:开始的动画开始前。
  2. v-enter-active:动画开始的执行过程。
  3. v-enter-to:开始的动画结束时。

离开

  1. v-leave-from:离开的动画开始前。
  2. v-leave-active:离开动画的执行过程。
  3. v-leave-to:离开动画的结束时。

既然我们已经知道了 transition 的 6 个状态,那接下来我们来看一下这个范例。

vue3 mike

这是一个用 transition做出来的轮播效果,我们来看一下怎麽做

<script>
import { ref } from 'vue'
export default {
  setup(){
    const imgIdx = ref(0);
    const slidList = ref([
        { id: '1', src: "https://source.unsplash.com/600x400?1" },
        { id: '2', src: "https://source.unsplash.com/600x400?2" },
        { id: '3', src: "https://source.unsplash.com/600x400?3" },
        { id: '4', src: "https://source.unsplash.com/600x400?4" },
        { id: '5', src: "https://source.unsplash.com/600x400?5" },
        { id: '6', src: "https://source.unsplash.com/600x400?6" },
        { id: '7', src: "https://source.unsplash.com/600x400?7" },
        { id: '8', src: "https://source.unsplash.com/600x400?8" },
    ]);
    
    const handleMenuActive = idx => {
      imgIdx.value = idx;
    };
    
    return {
      imgIdx,
      slidList,
      handleMenuActive,
    }
  }
};
</script>

我们会先把图片的资料 slidList 给定义出来,然後还有目前点到的按钮索引 imgIdx给定义出来。

<template>
    <div class="content">
        <div class="mid">
            <transition name="slids">
                <img 
                    v-for="(item, idx) in slidList" 
                    v-show="imgIdx === idx" 
                    :key="item.id" 
                    :src="item.src"
                />
            </transition>
        </div>
        <nav class="nav_menu">
            <a
                v-for="(item, idx) in slidList"
                :key="item.id"
                :class="{active: imgIdx === idx}"
                @click="handleMenuActive(idx)"
            >
            {{idx + 1}}
            </a>
        </nav>
    </div>
</template>

<style lang="scss">
    
    .slids-enter-active, .slids-leave-active{
        transition: transform .3s ease;
    }
    .slids-enter-from{
        transform: translateX(-550px);
    }
    .slids-leave-to{
        transform: translateX(550px)
    }
    
    .content {
        width: 600px;
        height: 400px;
        .mid{
            position: relative;
            width: 100%;
            height: 100%;
            overflow: hidden; 
            margin-bottom: 20px;
            img{
                position: absolute;
                top: 0;
                right: 0;
            }
        }
    }
    
    // 其他省略...
</style>

然後对里面的图片去用 v-for 搭配 v-show 来处理切换的部分,在这边要注意一下,图片的部分是透过 position: absolute;来定位,压在同一个地方,接下来仔细的来看一下 css 的部分

<style lang="scss">
    .slids-enter-active, .slids-leave-active{
        transition: transform .3s ease;
    }
    .slids-enter-from{
        transform: translateX(-550px);
    }
    .slids-leave-to{
        transform: translateX(550px)
    }
</style>

动画开始的执行过程( slids-enter-active )离开动画的执行过程 (slids-leave-active) 都给它使用 transition 来处理过场的部分,然後每个图片进来以前 slids-enter-from ,会被 transform 移到 -550px 这个位置 transform: translateX(-550px);,自然进场的时候就会滑动到原本的地方,再来图片离开的时候slids-leave-to,就会被 transform 移到 550px 跑出去外面,所以只要能掌握这些状态,你就用 transition 自由的控制你的组件要怎麽动。

但是现在会发生一个问题,就是使用 <transition></transition> 这个动画组件,里面的元件只能有一个,所以透过 v-for 回圈产生出来的 <img/>会无法执行,所以除了transition 这个动画组件以外,还有另外一个叫做 transition-group 的组件,就是专门处理 v-for 回圈产生出来的 DOM 去跑动态。

关於 transition-group 的官方文件 : https://v3.vuejs.org/api/built-in-components.html#transition-group

transition-grouptransition 的 API 一模一样,所以不用担心要改其他东西。

<transition-group name="slids">
    <img 
         v-for="(item, idx) in slidList" 
         v-show="imgIdx === idx" 
         :key="item.id" 
         :src="item.src"
    />
</transition-group>

这样一来就可以正常的执行了。

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

你以为结束了? 还没!!!

因为我们现在只能朝一个方向滑动,当我今天希望我点击的时候可以左右滑动,像是下面范例这样。

2

所以我必须要去判断说,我最新点击的按钮跟上一个按钮比,是在左边还是右边,然後我要去切换 transition-groupname ,来达到动画切换左右的部分。

首先我先定义一下左右滑的 css

<style lang="scss">
  .left-enter-active,
  .left-leave-active,
  .right-enter-active,
  .right-leave-active {
      transition: transform .3s ease;
  }
  
  .left-enter-from{
      transform: translateX(-550px);
  }
  .left-leave-to{
      transform: translateX(550px)
  }
  .right-enter-from{
      transform: translateX(550px)
  }
  .right-leave-to{
      transform: translateX(-550px);
  }
</style>

你会看到我的 class 开头被改成了 leftright,所以可想而知,我们的 transition-groupname 要切换 leftright

所以在这边我新定义了两个东西

const prevIdx = ref(0);           // 纪录上一个点击的按钮索引
const transType = ref("right");   // 当前的 transition-group 的 name

然後在我们点击按钮的 function 加入一下判断

const handleMenuActive = idx => {
    imgIdx.value = idx;
    transType.value = idx > prevIdx.value ? "left" : "right";
}

这边我会判断当我点击的索引比上一个索引大的时候,那就是往左滑,不然就右滑,但问题来了,我要什麽时候去纪录上一个索引的值呢? 不能在 click 的时候,不然会同步的去写入,所以有人会说,那等它慢一点在写入,所以写一个 setTimeout 就好了,拜托不要XDDDDD

transitiontransition-group 有提供动画的 Lifecycle Hooks,所以这次我们要使用 after-leave这个 hooks,after-leave 是当你动画执行结束之後会触发,所以我们要在 after-leave 的时候去写入我们的 prevIdx.value

const afterLeave = () => {
    prevIdx.value = imgIdx.value;
}

return {
    afterLeave,
}

定义好了 prevIdx.value的 callback 的 function 後,直接 on 这个 hooks

<transition-group 
    :name="transType"
    @after-leave="afterLeave"
>
    <img 
         v-for="(item, idx) in slidList" 
         v-show="imgIdx === idx" 
         :key="item.id" 
         :src="item.src"
    />
</transition-group>

这样一来就可以让你在 click 的时候,去切换 transition-group 的 name,以达到左右换图的效果。

<script>
import { ref } from 'vue'
export default {
  setup(){
    const imgIdx = ref(0);
    const prevIdx = ref(0);
    const transType = ref("right");

    const slidList = ref([
        { id: '1', src: "https://source.unsplash.com/600x400?1" },
        { id: '2', src: "https://source.unsplash.com/600x400?2" },
        { id: '3', src: "https://source.unsplash.com/600x400?3" },
        { id: '4', src: "https://source.unsplash.com/600x400?4" },
        { id: '5', src: "https://source.unsplash.com/600x400?5" },
        { id: '6', src: "https://source.unsplash.com/600x400?6" },
        { id: '7', src: "https://source.unsplash.com/600x400?7" },
        { id: '8', src: "https://source.unsplash.com/600x400?8" },
    ]);
    
    const handleMenuActive = idx => {
      imgIdx.value = idx;
      transType.value = idx > prevIdx.value ? "left" : "right";
    }
    
    const afterLeave = () => {
      prevIdx.value = imgIdx.value;
    }
    
    return {
      imgIdx,
      slidList,
      handleMenuActive,
      afterLeave,
      transType
    }
  }
};
</script>

<template>
    <div class="content">
        <div class="mid">
            <transition-group 
              :name="transType"
              @after-leave="afterLeave"
            >
                <img 
                    v-for="(item, idx) in slidList" 
                    v-show="imgIdx === idx" 
                    :key="item.id" 
                    :src="item.src"
                />
            </transition-group>
        </div>
        <nav class="nav_menu">
            <a
                v-for="(item, idx) in slidList"
                :key="item.id"
                :class="{active: imgIdx === idx}"
                @click="handleMenuActive(idx)"
            >
            {{idx + 1}}
            </a>
        </nav>
    </div>
</template>

<style lang="scss">
  .left-enter-active,
  .left-leave-active,
  .right-enter-active,
  .right-leave-active {
      transition: transform .3s ease;
  }
  
  .left-enter-from{
      transform: translateX(-550px);
  }
  .left-leave-to{
      transform: translateX(550px)
  }
  .right-enter-from{
      transform: translateX(550px)
  }
  .right-leave-to{
      transform: translateX(-550px);
  }
  // 其他省略...
</style>

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


所以我们在处理动画的时候不要再依赖 JQuery 的 animate 了,在使用 Vue 上面有更多好用的方式可以处理动态效果。

好啦 ! 今天篇幅也够长了,先介绍两个做法,明天我们在来讨论另外两个动画的处理方式。

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


<<:  [拯救上班族的 Chrome 扩充套件] 来说说文章走向和目标

>>:  【从零开始的Swift开发心路历程-Day4】Xcode介面基础介绍

Day 6 : HTML - 网页排版超强神器part_2,CSS grid到底是什麽?

上篇介绍了CSS Flex,这篇想来聊聊CSS grid到底是什麽东西 这里想先给大家一个观念: F...

Day 17 - Linux 上设定 PBR

我们需要使用 FRRouting,若还没安装的话,请先安装一下 这次使用的系统为 Ubuntu 20...

apt-get upgrade 和dist-upgrade的差别

Debian/Ubuntu Linux都使用apt,升级时都是: apt-get update ap...

第二十九天:做一个总结吧

嗨大家我是Andy,今天来到了第二十九天,我们像以前整理一下这几天所学的,然後明天应该就是完赛心得了...

[Day29] 几个精进Action的建议

到这里,你已经建立一个具备完整对话体验的Action 也已经藉由GCP建构符合使用情境的架构了 接...