iT邦幫忙

2021 iThome 鐵人賽

DAY 30
1
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 30

不只懂 Vue 語法:請用圖片輪播的例子示範 Composition API?

問題回答

這個例子會示範以 Compositions API 開發一個簡單的圖片輪播。先打 API 從遠端取得資料,之後把資料渲染到畫面,並加入輪播功能,而且輪播功能使用了 Vue 的 transition 來製作。

此例子是參考六角學院的直播裏 Kuro 大大提到的常見面試題之一以及他的例子做法,並加入遠端抓資料(從 random user 抓取 5 筆 user 資料)的功能和客製樣式來改寫。

完成結果

先展示完成效果:

以下會說明步驟,但只會針對重點部分來解說。

打 API 取得資料

首先,先從 random user 取得資料:

import { reactive } from 'vue';

export default {
  setup() {

    const users = reactive({ results: [] });

    (async () => {
      try {
        const res = await fetch('https://randomuser.me/api/?results=5');
        const resJSON = await res.json();
        users.results = resJSON.results;
      } catch (err) {
        console.log(err);
      }
    })();
    
    return {
      users
    };
  }
};

在 Vue 3,setup 函式的生命週期等於 created,因此可以直接在 setup 裏打 API 取資料。或者使用 onMountedonBeforeMount 也可以。

把資料渲染到網頁上,並使用 currentIndex 來指定見前要顯示哪筆資料 :

<!-- 需要加上 v-if 判斷是否已抓到遠端的資料,不然會報錯 -->
<div v-if="users.results[currentIndex]" class="card">
<div class="card-img">
  <transition :name="imgTransition">
    <img
      :key="users.results[currentIndex]"
      :src="users.results[currentIndex].picture.large"
    />
  </transition>
</div>
<ul>
  <li>
    {{ users.results[currentIndex].name.title }}
    {{ users.results[currentIndex].name.last }}
    {{ users.results[currentIndex].name.first }}
  </li>
  <li>{{ users.results[currentIndex].email }}</li>
  <li>{{ users.results[currentIndex].phone }}</li>
</ul>
</div>

注意,最外層需要加上 v-if 來判斷是否已抓到遠端的資料。因為在打 API 取資料是非同步程式。因此,即使我們在 setup 裏已執行打 API的程式碼,但在接收到資料之前,程式會一直繼續執行,包括渲染畫面。當畫面渲染好但資料還沒回來時,就不能在畫面渲染資料,不然會出現例如是 Cannot read property 'name' of undefined 這種錯誤。

transition 的部分會留待最後才解說。

新增按鈕功能

按鈕功能包括:

  • 根據目前資料的數量,印出同等數量的按鈕。
  • 加入 next、prev 按鈕,前後切換顯示資料。
  • 按下單一按鈕時,會切換顯示該筆資料。
<div class="carousel-btns">
<a @click.prevent="swipeSlide('prev')" href="#"> prev </a>
<div class="carousel-btn-group">
  <a
    @click.prevent="selectSlide(index)"
    href="#"
    v-for="(n, index) in users.results.length"
    :key="n"
    :class="[
      { 'carousel-btn-active': currentIndex === index },
      'carousel-btn'
    ]"
  >
  </a>
</div>
<a @click.prevent="swipeSlide('next')" href="#"> next </a>
</div>
setup() {
    const imgTransition = ref('');
    const currentIndex = ref(0);
    
    // ...
    
    
    // next, prev 切換資料功能
    const swipeSlide = direction => {
      imgTransition.value = `${direction}-img`;
      if (direction === 'next') {
        currentIndex.value = (currentIndex.value + 1) % users.results.length;
      } else {
        currentIndex.value === 0
          ? (currentIndex.value = users.results.length - 1)
          : currentIndex.value--;
      }
    };

    // 指定顯示單一資料功能
    const selectSlide = index => {
      if (currentIndex.value > index) {
        imgTransition.value = 'prev-img';
      } else {
        imgTransition.value = 'next-img';
      }
      currentIndex.value = index;
    };
    
    return {
      users,
      currentIndex,
      swipeSlide,
      selectSlide,
      imgTransition
    };
}

關於 next, prev 切換資料功能,如果 direction 是 next,寫法是:

currentIndex.value = (currentIndex.value + 1) % users.results.length;

這寫法是為了當 currentIndex 的數值大於 users 陣列長度時,即代表要切回第一筆資料。currentIndex 是由 0 為開始,這時候會顯示第一筆資料。目前資料長度是 5,因此,當顯示最後一筆資料時, currentIndex 會是 4 。如果再按下 nextcurrentIndex 會加一變成 5,而 5 % 5 的餘數會是 0,因此回到第一筆資料。其餘頁碼,例如 1, 2。即是 1 % 52 % 5 等,答案會是 1, 2。即是等於該頁碼的數值。

動畫設定

有兩點要處理:

  • 外層設定 overflow: hidden
  • 使用 transition 標籤設定動畫

外層設定 overflow: hidden

切換圖片時,當前一張圖片還沒完全滑走,它就會繼續佔用位置,因此即將要顯示的圖片就會被推擠到下面,情況如下:

因為此例中的img 是被 transition 包著,transition 在切換圖片時,會在舊 img 後再加即將要顯示的新 img

transition 設定

切換資料時,使用了 transition 來製作切換動畫。

<div class="card-img">
  <transition :name="imgTransition">
    <img
      :key="users.results[currentIndex]"
      :src="users.results[currentIndex].picture.large"
    />
  </transition>
</div>

因此新的 img 會被推擠到下面。解決方法就是在 card-img 設定 overflow: hidden

setup() {
    const swipeSlide = direction => {
      imgTransition.value = `${direction}-img`;
      // ...
    };
    
    const selectSlide = index => {
      if (currentIndex.value > index) {
        imgTransition.value = 'prev-img';
      } else {
        imgTransition.value = 'next-img';
      }
      currentIndex.value = index;
    };
}

SCSS:

.prev-img-enter-active,
.prev-img-leave-active,
.next-img-enter-active,
.next-img-leave-active {
  transition: all 0.5s;
}

.next-img-leave-to {
  transform: translate(-100%);
}

.prev-img-leave-to {
  transform: translate(100%);
}

有幾點要注意:

  • 如果切換的元素,它們擁有相同的標籤,就需要加上 key 屬性,否則動畫會無效。在此例中,切換的元素全都是 img,因此要加上 key。
  • transition 會在舊和新的 DOM 上加入相應的 class。Class 名稱可以使用 <transition name=""> 的 name 屬性來定義,因此,當 name 的值是 next-img 時,所有 transition 用到的 class 名稱就會以 next-img 作為前輟,例如是 next-img-leave-activenext-img-leave-to
  • 此例中,當 currentIndex 是前進,動畫就會向左滑動。相反的話,就是向右滑動。

關於動畫 class 名稱的作用,看此圖就懂了:

截圖自官方文件

因此,當圖片要消失時,如果要設定它的目的地,對應的 class 就是 xxx-leave-to

完整程式碼

https://codesandbox.io/s/composition-api-tu-pian-lun-bo-x27ht?file=/src/App.vue

總結

  • 如果一載入頁面時就要打 API 取資料,可在 setuponMountedonBeforeMount 裏進行。
  • 因為打 API 是非同步程式,因此如果畫面需要操作那些資料,可以使用 v-if 避免資料還沒抓到的情況。
  • 可以使用取餘數 % 的方式來製作循環的頁碼。
  • 使用 transition 切換動畫時,會同時存在舊和新的 DOM,也會為它們加上不同的 class。
  • class 名稱的前輟是根據你在 <transition name=""> 的 name 屬性。如沒有指明,預設就會是 v-xxxx
  • 如果 transition 裏切換的元素有同一標籤,就需使用 key 屬性。

呼~ 終於到了最後一天了,感謝團友和讀者,技術分享就到此為止了,明天會總結一下這 30 天的鐵人賽心得,會以個人感受與心得為主,明天見~


上一篇
不只懂 Vue 語法:試說明 Composition API 與 Options API 概念和語法的分別?
下一篇
不只懂 Vue 語法:後記 - 為自己堅持 30 天的參賽心得
系列文
不只懂 Vue 語法:Vue.js 觀念篇31

尚未有邦友留言

立即登入留言