本篇要來介紹當使用者滑到該圖或該組件時,才載入圖或組件的應用
還記得我們在16篇以及17篇分別提到
▲ 而16篇只提到Javascipt的寫法,因此這篇我們要將程式碼改為vue3 + IntersectionObserver API寫法
及
異步組件是指在需要的時候才會被加載和渲染的組件,這樣可以提高應用程序的性能
▲ 因此這篇我們要加可視範圍情境並使用vue3 + defineAsyncComponent +IntersectionObserver API寫法
首先先來做圖片懶加載,
功能大綱
▲ 成果
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
(代表只要圖片可見,就會加載)本範例這樣設計只是為了讓你明白,在還沒判定要加載時,預設會是走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)來判斷是否渲染?
動態載入組件,還是在需要的時候才能判斷是否加載,而什麼時候是需要的時候?通常是指以下幾種情況:事件觸發條件(例如點擊、滑鼠懸停、時間延遲...等)或是條件判斷,是讓組件從 "不載入" 變為 "載入" 的觸發因素。
那我們明天再見了~