Vue3實現圖片懶加載及自定義懶加載指令
- 前言
- 1.使用vue3-lazyload插件
- 2.自定義v-lazy懶加載指令
- 2.1 使用VueUse
- 2.2 使用IntersectionObserver
前言
圖片懶加載是一種常見性能優化的方式,它只去加載可視區域圖片,而不是在網頁加載完畢后就立即加載所有圖片,能減少很多不必要的請求,極大的提升用戶體驗。
圖片懶加載的實現原理:在圖片沒進入可視區域的時候,只需要讓 img 標簽的 src 屬性指向一張默認圖片,在它進入可視區后,再替換它的 src 指向真實圖片地址即可。
本文就分享一下在vue3中實現圖片懶加載的幾種方式,包括使用插件以及自定義指令,實現的最終效果如下圖所示:
1.使用vue3-lazyload插件
第一種方式就是使用插件,使用插件的方式非常簡單,只需要簡單的幾步即可實現。
Vue2中可以使用vue-lazyload插件來實現圖片懶加載,在Vue3中可以使用vue3-lazyload插件實現圖片懶加載。
- 1.安裝vue3-lazyload插件
$ npm i vue3-lazyload
# or
$ yarn add vue3-lazyload
# or
$ pnpm i vue3-lazyload
- 2.main.js入口文件注冊插件
import { createApp } from "vue";
import App from "./App.vue";
//引入圖片懶加載插件
import Lazyload from "vue3-lazyload";const app = createApp(App);//注冊插件
app.use(Lazyload, {loading: "@/assets/images/default.png",//可以指定加載中的圖像error: "@/assets/images/err.png",//可以指定加載失敗的圖像
});app.mount("#app");
- 3.模板中使用v-lazy指令來延遲加載圖像
<template><ul class="container"><li v-for="item in imgList" :key="item.id"><img v-lazy="item.url" class="item" /></li></ul>
</template><script lang="ts" setup>
import { reactive } from "vue";
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => {return {id: `${i}`,url: `@/assets/images/${i}.jpg`,};
});
const imgList = reactive(data);
</script><style scoped lang="scss">
.container {width: 100vw;height: 100vh;overflow: auto;.item {width: 100%;height: 200px;}
}
</style>
2.自定義v-lazy懶加載指令
- 下面一種方式是自定義一個懶加載的指令,如何實現呢?
圖片懶加載的核心是監聽圖片是否進入可視區域,如果進入就替換src,即懶加載指令的核心。
網上看了很多教程,大多都使用Element.getBoundingClientRect()這個方法,該方法返回一個 DOMRect 對象,提供了元素的大小及其相對于視口的位置,然后監聽滾動條事件,通過img.getBoundingClientRect()進行一系列的比較來判斷圖片是否在視口內,這種方式略顯復雜。其實,只要我們能夠簡化判斷圖片是否進入可視區域這一流程,實現一個自定義的懶加載指令就很簡單了。
- 有沒有什么簡化的方式呢?
可以通過VueUse中的useIntersectionObserver和原生的IntersectionObserver api來簡化判斷圖片是否進入可視區域,下面就分別通過這兩種簡化的方式來實現一個自定義的懶加載指令。
2.1 使用VueUse
VueUse 是什么?
一款基于Vue組合式API的函數工具集。
以上是官方網站關于它的定義。
簡單的說就是一個工具函數包,它可以幫助你快速實現一些常見的功能。比如下面的一些:
- useLocalStorage:提供在本地存儲中保存和獲取數據的功能。
- useMouse:提供跟蹤鼠標位置和鼠標按下狀態的功能。
- useDebounce:提供防抖功能。
- useThrottle:提供節流功能。
- useIntersectionObserver:提供對元素是否可見進行觀察的功能,可用于實現懶加載等效果。
本文要用到的就是其中的useIntersectionObserver這個函數,來監聽圖片的可見性。
- 首先安裝 VueUse
npm i @vueuse/core
- main.js入口文件導入
import { createApp } from "vue";
import App from "./App.vue";//從@vueuse/core中導入useIntersectionObserver函數
import { useIntersectionObserver } from "@vueuse/core";const app = createApp(App);app.mount("#app");
- directive注冊v-lazy全局指令
//main.js
//注冊v-lazy全局指令,使v-lazy在所有組件中都可用
app.directive("lazy", {//節點掛載完成后調用mounted(el, binding) {useIntersectionObserver(el, ([{ isIntersecting }]) => {//判斷當前監聽元素是否進入視口區域if (isIntersecting) {el.src = binding.value;}});},
});
一個指令定義對象可以提供多個鉤子函數,比如 mounted、updated、unmounted 等,我們使用mounted,也就是在節點掛載完成后調用。指令的鉤子有兩個主要的參數:el和binding。el是指令綁定到的元素,binding中使用最多的是value,即傳遞給指令的值,例如在 v-lazy=“imgSrc” 中,值是 imgSrc對應的真實圖片地址。
然后使用useIntersectionObserver函數,它的兩個參數,一個是需要監聽的元素,另一個是回調函數,參數值isIntersecting為一個布爾值,用來判斷當前監聽元素是否進入視口區域,如果進入視口區域,那么我們就可以將圖片的真實url賦值給圖片的src。
其實上述代碼還有不完善的地方,首先是重復監聽的問題,可以進行console調試一下:
useIntersectionObserver(el, ([{ isIntersecting }]) => {console.log(isIntersecting);//測試if (isIntersecting) {el.src = binding.value;}
});
此時的效果如下圖所示:
從上圖可以看到,往上滾動,監聽過的圖片會重復監聽,這是我們不想要的,會造成性能浪費。
解決思路:在監聽的圖片第一次完成加載后就停止監聽。可以利用useIntersectionObserver函數提供的stop方法,修改后的代碼如下:
app.directive("lazy", {mounted(el, binding) {const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {console.log(isIntersecting);if (isIntersecting) {el.src = binding.value;//在監聽的圖片第一次完成加載后就停止監聽stop();}});},
});
完善后的效果如下,解決了重復監聽問題。
我們還可以設置一個默認圖片,當圖片還沒加載完成時,就顯示默認圖片。
app.directive("lazy", {mounted(el, binding) {el.src = "@/assets/images/default.png"; // 使用默認圖片const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {if (isIntersecting) {el.src = binding.value;//在監聽的圖片第一次完成加載后就停止監聽stop();}});},
});
此時還存在著的一個問題是,當前注冊了一個全局的自定義指令,所有的代碼邏輯全寫在入口文件中,這樣會造成代碼的臃腫。
解決思路:拆分代碼,通過插件的方法把懶加載指令封裝為插件,main.js入口文件只需負責注冊插件即可。
src下新建directive/index.js文件,專門存放自定義的插件,把代碼邏輯進行轉移。
// src/directive/index.js
import { useIntersectionObserver } from "@vueuse/core";
// 封裝插件
export const lazyPlugin = {install(app) {app.directive("lazy", {mounted(el, binding) {el.src = "@/assets/images/default.png"; const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {if (isIntersecting) {el.src = binding.value;stop();}});},});},
};
然后在main.js中注冊插件
import { createApp } from "vue";
import App from "./App.vue";
import { lazyPlugin } from "./directive";const app = createApp(App);
//注冊插件
app.use(lazyPlugin);
app.mount("#app");
定義插件可以參考Vue官網。通常一個 Vue3 的插件會暴露 install 函數,當 app 實例 use 該插件時,就會執行該函數。然后在 install 函數內部,通過 app.directive 去注冊一個全局指令,這樣就可以在組件中使用它們了。
現在的效果就和一開始介紹的效果一致了。
2.2 使用IntersectionObserver
其實查看vue3-lazy源碼和useIntersectionObserver源碼,會發現,它們使用的就是原生IntersectionObserver api。那么接下來我們也可以使用這個api來實現一個自定義的懶加載指令。
- MDN:IntersectionObserver 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視口交叉狀態的方法。當它被創建時,其被配置為監聽根中一段給定比例的可見區域。當其監聽到目標元素的可見部分(的比例)超過了一個或多個閾值(threshold)時,會執行指定的回調函數。
簡單來說就是IntersectionObserver可以來判斷圖片是否進入可視區。
它對應的回調函數的參數 entries,是 IntersectionObserverEntry 對象數組。當觀測的元素可見比例超過指定閾值時,就會執行該回調函數(默認閾值為 0,表示目標元素剛進入根元素可見范圍時觸發回調函數),對 entries 進行遍歷,拿到每一個 entry,然后判斷 entry.isIntersecting 是否為 true,如果是則說明 entry 對象對應的 DOM 元素進入了可視區。
具體代碼如下:
// src/directive/index.js
import defaultImg from "@/assets/images/default.png";
//定義一個數組用來存儲尚未加載的圖片
let imgsList = [];//加載圖片
function loadingImg(imgDOM) {//獲得圖片的srclet imgSrc = imgsList.filter((item) => item.el === imgDOM)[0].src;//新建Image對象實例來代替當前圖片的加載,圖片加載完畢就會觸發onload事件,替換img元素的src屬性const img = new Image();img.src = imgSrc; img.onload = function () {// 當圖片加載完成之后 替換img元素的src屬性imgDOM.src = imgSrc;};//將已加載好的圖片從數組中刪除imgsList = imgsList.filter((item) => item.el !== imgDOM);
}const io = new IntersectionObserver((entries) => {entries.forEach((item) => {// isIntersecting屬性判斷目標元素當前是否可見if (item.isIntersecting) {//加載圖片,加載完后停止監聽loadingImg(item.target);io.unobserve(item.target); }});
});export const lazyPlugin = {install(app) {app.directive("lazy", {mounted(el, binding) {el.src = defaultImg; // 使用默認圖片io.observe(el); //監聽圖片imgsList.push({ el: el, src: binding.value }); //數組中加入當前圖片},beforeUnmount(el) {//某個img元素解綁時,停止監聽,從數組中刪除io.unobserve(el);imgsList = imgsList.filter((item) => item.el !== el);},});},
};
主要思路就是:定義一個數組用來存儲尚未加載的圖片,observe方法對每個圖片進行監聽,如果當前圖片在可視區域,就加載圖片,并且從數組中刪除圖片,然后unobserve停止監聽。
最后依然需要在main.js中注冊插件,即可使用v-lazy自定義指令。
參考資料:
https://www.npmjs.com/package/vue3-lazyload
https://cn.vuejs.org/guide/reusability/custom-directives.html
https://cn.vuejs.org/guide/reusability/plugins.html
https://www.vueusejs.com/core/useIntersectionObserver/
https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
好了,以上就是本文的全部內容,如有問題,歡迎指出!