iT邦幫忙

2023 iThome 鐵人賽

DAY 18
1
Vue.js

Vue3歡樂套件箱耶系列 第 18

開箱18:圖片/元件懶加載~vue3 + IntersectionObserver API範例應用

  • 分享至 

  • xImage
  •  

本篇要來介紹當使用者滑到該圖或該組件時,才載入圖或組件的應用

還記得我們在16篇以及17篇分別提到

  • 圖片懶加載
  • IntersectionObserver API

▲ 而16篇只提到Javascipt的寫法,因此這篇我們要將程式碼改為vue3 + IntersectionObserver API寫法

  • defineAsyncComponent(Vue.js 中用來定義異步組件的方法)

異步組件是指在需要的時候才會被加載和渲染的組件,這樣可以提高應用程序的性能

▲ 因此這篇我們要加可視範圍情境並使用vue3 + defineAsyncComponent +IntersectionObserver API寫法

首先先來做圖片懶加載,

圖片懶加載(vue3 + 原生IntersectionObserver API)

功能大綱

  • 當使用者滑到該圖位置時,判斷是否為進入50%,才載入該張圖片的資源


▲ 成果
Demo網址:https://hahasister-ironman-project.netlify.app/#/intersectionObserverImg

(單一組件內)

<template>
    ...
    <div class="image-container">
      <img
        v-for="(image, index) in images"
        src="/src/assets/icon/loading-bar.png"
        :key="image.id"
        :data-src="image.src"
        :alt="image.alt"
        :ref="itemRef(index)"
        width="400"
        height="400"
      />
    </div>
    ...
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { images } from '../lib/imgList'; // 引入照片清單

const itemRefs = ref([]); // img的DOM陣列

const observer = new IntersectionObserver(
  (entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) { // 如果在可見範圍
        const img = entry.target;
        const src = img.getAttribute('data-src');

        if (src) {
          img.src = src; // 將圖片路徑改為真正的路徑
          img.removeAttribute('data-src');  //移除不需要的屬性值
          observer.unobserve(img); // 不再監視已經加載的圖片
        }
      }
    });
  },
  {
    threshold: 0.5 // 在一半的圖片進入可見區域時觸發
  }
);

onMounted(() => {
  itemRefs.value.forEach(img => { // 將每一個img進入觀察
    if (img) {
      observer.observe(img);
    }
  });
});

const itemRef = index => el => {
  if (el) {
    itemRefs.value[index] = el; // 匯入每一個img的DOM
  }
};
</script>

<style lang="scss" scoped>
img {
  display: block;
  margin: 1em 0;
}
</style>

這樣就完成拉!當然程式碼還需優化,這只是簡單的範例

程式碼有以下重點

  • threshold: 0.5
    判斷圖片是否也進入50%了?這個值是本範例故意設計的(實務上不會這麼做)
    若沒有特別設定threshold則是0(代表只要圖片可見,就會加載)

本範例這樣設計只是為了讓你明白,在還沒判定要加載時,預設會是走src=loading的圖,判斷該載入時才是原本的src

  • data-src 是自訂資料屬性,只是為了將圖片網址暫存在這個屬性內,之後方便取得

  • 使用ref來獲取 DOM 元素
    還記得監控的一個元素時,要使用observer.observe(需要監控的DOM)在Javascript中我們可以用 document.get...來取得,而在vue中可以用ref來獲取 DOM 元素,所以當< img >有多個時,我們其中一種寫法可以

<img
  v-for="(image, index) in images"
  :ref="itemRef(index)"
  ...
/>

也就是每次 v-for 迴圈創建一個新的< img >元素時,itemRef(index) 會被調用,並返回一個新的函數。這個返回的函數會接收一個參數 el,這個 el 就是剛剛創建的< img >元素的實際 DOM 節點。
當這個< img >元素被插入到 DOM 中後,Vue 會自動調用這個返回的函數,並將 el 作為參數傳入。在這個函數內部,它會檢查 el 是否存在,如果存在,就會將它存儲到 itemRefs.value 陣列的相應索引位置。

接著我們來做簡易版的組件懶加載

組件懶加載

功能大綱

  • 當使用者滑到該組件位置時,才載入該組件的資源

    ▲成果

Demo網址:https://hahasister-ironman-project.netlify.app/#/intersectionObserverComponent

(組件內)
今天的寫法,概念跟17篇差不多,

<template>
  <div>
    <div ref="componentA" class="mb-[600px] w-[500px] h-[500px]">
      <AsyncComponentA v-if="loadedA" />
    </div>
    <div ref="componentB" class="mb-[600px] w-[500px] h-[500px]">
      <AsyncComponentB v-if="loadedB" />
    </div>
    <div ref="componentC" class="w-[500px] h-[500px]">
      <AsyncComponentC v-if="loadedC" />
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, defineAsyncComponent } from 'vue';
const componentA = ref(null);
const componentB = ref(null);
const componentC = ref(null);

const loadedA = ref(false);
const loadedB = ref(false);
const loadedC = ref(false);

const AsyncComponentA = defineAsyncComponent(() =>
  import('../components/AsyncComponent/AsyncComponentA.vue')
);
const AsyncComponentB = defineAsyncComponent(() =>
  import('../components/AsyncComponent/AsyncComponentB.vue')
);
const AsyncComponentC = defineAsyncComponent(() =>
  import('../components/AsyncComponent/AsyncComponentC.vue')
);

onMounted(() => {
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        switch (entry.target) {
          case componentA.value:
            loadedA.value = true;
            observer.unobserve(componentA.value); // 停止觀察
            break;
          case componentB.value:
            loadedB.value = true;
            observer.unobserve(componentB.value);
            break;
          case componentC.value:
            loadedC.value = true;
            observer.unobserve(componentC.value);
            break;
        }
      }
    });
  });

  observer.observe(componentA.value);
  observer.observe(componentB.value);
  observer.observe(componentC.value);
});
</script>

這樣就完成拉!當然程式碼還需優化,這只是簡單的範例

程式碼有以下重點

  • 元素一開始沒有設定寬高會有什麼關係?
    如果你的 div 元素一開始沒有設定寬高,那麼 IntersectionObserver 可能會立即觸發 isIntersecting 為 true,因為還沒載入之前這些 div 元素沒有寬高會被認為是在視窗內。

  • 不是寫了defineAsyncComponent就有動態載入?為什麼還要寫變數(v-if)來判斷是否渲染?
    動態載入組件,還是在需要的時候才能判斷是否加載,而什麼時候是需要的時候?通常是指以下幾種情況:事件觸發條件(例如點擊、滑鼠懸停、時間延遲...等)或是條件判斷,是讓組件從 "不載入" 變為 "載入" 的觸發因素。

那我們明天再見了~


上一篇
開箱17:是不是跟我一樣困惑異步加載???defineAsyncComponent範例應用
下一篇
開箱19:所以Import是?陪我一起觀念釐清~
系列文
Vue3歡樂套件箱耶30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言