Vue2?中自定義圖片懶加載指令?v-lazy實例詳解
引言
由于我在開發(fā)的個人博客前臺頁面時,想優(yōu)化網站的響應速度,所以想實現(xiàn)圖片懶加載效果。
我是通過自定義指令v-lazy
實現(xiàn)的,所以在這跟大家分享一下這個指令的開發(fā)流程及其難點的解決方法。
1.涉及到的主要知識講解
自定義圖片懶加載指令主要涉及以下三塊知識:
- Vue2 中自定義指令
- 使用事件總線進行模塊之間的通信
- 使用到的 Web API
- Element.clientHeight
- Element.getBoundingClientRect()
下面我會對這些知識點進行一一介紹。
1.1 Vue2 中自定義指令
下面我只對自定義指令做簡單的介紹,詳細介紹大家可以參照Vue 官網 - 自定義指令。
1.1.1 指令對象的鉤子函數(shù)
- bind:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
- inserted:被綁定元素插入父節(jié)點時調用 (僅保證父節(jié)點存在,但不一定已被插入文檔中)。
- update:所在組件的 VNode 更新時調用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒有??赏ㄟ^比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數(shù)參數(shù)見下)。
- componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調用。
- unbind:只調用一次,指令與元素解綁時調用。
鉤子函數(shù)的參數(shù)主要有這四個el、binding、vnode、oldVnode
。
1.1.2 鉤子函數(shù)參數(shù)
- el:指令所綁定的元素,可以用來直接操作 DOM。
- binding:一個對象,包含以下 property:
- name:指令名,不包括 v- 前綴。
- value:指令的綁定值,如:v-my-directive="1 + 1" 中,綁定值為 2。
- oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
- expression:字符串形式的指令表達式。如 v-my-directive="1 + 1" 中,表達式為 "1 + 1"。
- arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。
- modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象為 { foo: true, bar: true }。
- vnode:Vue 編譯生成的虛擬節(jié)點。移步 VNode API 來了解更多詳情。
- oldVnode:上一個虛擬節(jié)點,僅在 update 和 componentUpdated 鉤子中可用。
1.2 使用事件總線進行模塊之間的通信
對事件總線不熟悉的朋友,可以參照該博客什么是 Vue 事件總線(EventBus)。
- 監(jiān)聽事件總線上的事件---調用 $on 方法
- 觸發(fā)事件總線上的事件---調用 $emit 方法
- 取消監(jiān)聽事件總線上的事件---調用 $off 方法
我們可以借助 vue 示例來實現(xiàn)事件總線,也可以自行封裝;我使用了第一種方法。
因此事件總線配置文件---eventBus.js
的代碼如下:
import Vue from "vue"; const eventBus = new Vue({}); /* * 事件名:mainScroll * 含義:主區(qū)域滾動條位置變化后觸發(fā) * 參數(shù): * - 滾動的dom元素,如果是undefined,則表示dom元素已經不存在 */ //在Vue.prototype原型上注冊事件總線,方便vue實例對象監(jiān)聽和觸發(fā) Vue.prototype.$bus = eventBus; //導出事件總線,方便在其他js模塊監(jiān)聽和觸發(fā)事件總線上的事件 export default eventBus;
1.3 使用到的 Web API
1.3.1 Element.clientHeight
首先Element.clientHeight
是一個只讀屬性,具有以下特點:
- 對于那些沒有定義 CSS 或者內聯(lián)布局盒子的元素,該 API 會返回 0;
- 對于根元素(html 元素)或怪異模式下的 body 元素,該 API 將返回視口高度(不包含任何滾動條)
- 其他情況,該 API 會返回元素內部的高度(以像素為單位),包含
content
與padding
,不包含border
、margin
與水平滾動條(如果存在)。
另外改 API 會將獲取的值四舍五入取整數(shù)。如果你需要小數(shù)結果,可以使用 element.getBoundingClientRect()方法。
示例圖如下:
該 API 的詳細文檔可參照MDN - Element.clientHeight。
1.3.2 Element.getBoundingClientRect()
Element.getBoundingClientRect()
方法返回一個DOMRect
對象,其提供了元素的大小及其相對于視口的位置。 該方法無參數(shù),返回值為DOMRect
對象,該對象的屬性以下幾個:
- width:就是元素自身寬度
- height: 元素自身高度
- left(x):元素開始位置到窗口左邊的距離
- right: 元素的右邊到窗口左邊的距離
- bottom: 元素的下邊到窗口上邊的距離
- top(y): 元素的上邊到窗口上邊的距離
- x 和 y 相當于 left 和 top
示意圖如下:
該 API 的詳細文檔可以參照MDN - Element.getBoundingClientRect()
2.圖片懶加載指令的基本介紹
2.1 最終的實現(xiàn)效果
最終效果如下圖:
2.2 圖片懶加載指令的注冊與使用
由于在個人博客系統(tǒng)中圖片懶加載指令使用的比較頻繁,使用我選擇了全局注冊該指令。
另外因為我使用事件總線這方法來自己通信,使用還需引入事件總線配置文件---eventBus.js
所以 main.js入口文件
的代碼如下:
import Vue from "vue"; import App from "./App.vue"; import "./eventBus"; //引入事件總線 import vLazy from "./directives/lazy"; Vue.directive("lazy", vLazy); //全局注冊指令 new Vue({ render: (h) => h(App), }).$mount("#app");
使用 v-lazy 指令的示例代碼如下:
<template> <div class="container"> <ul> <li v-for="img in imgs" :key="img.id"> <img v-lazy="img.src" :alt="img.alt" :title="img.title" /> </li> </ul> </div> </template> <script> export default { data() { return { imgs: [ { id: "", src: "", alt: "", title: "", }, ], }; }, //下面的代碼可以用組件混入來進行封裝,從而優(yōu)化代碼結構 methods: { //觸發(fā)mainScroll事件 handleMainScroll() { this.$bus.$emit("mainScroll", this.$refs.container); }, }, mounted() { //監(jiān)聽滾輪事件 this.$refs.container.addEventListener("scroll", this.handleMainScroll); }, beforeDestroy() { this.$bus.$emit("mainScroll");//參數(shù)傳入undefined,表示dom元素已經不存在 //取消監(jiān)聽滾輪事件 this.$refs.container.removeEventListener("scroll", this.handleMainScroll); }, }; </script>
3. 實現(xiàn)圖片懶加載的原理
要實現(xiàn)圖片懶加載效果,我們首先要思考以下四個關鍵問題:
- 如何監(jiān)聽容器的滾動條的滾動?
- 使用自定義指令哪些鉤子函數(shù)?
- 如何判斷圖片 img 元素是否在用戶的可見范圍內?
- 如何處理圖片 img 元素的加載?
3.1 如何監(jiān)聽容器的滾動條的滾動?
對于這問題,由于我的博客系統(tǒng)在處理其他組件之間的傳值問題時,使用了事件總線方法,所以為了方便,我也使用這一方法,當然大家可以針對實際場景使用其他方法來解決這問題。
所以我們要在 v-lazy 圖片懶加載指令配置文件---lazy.js
文件中監(jiān)聽事件總線 eventBus 中的mainScroll事件
,同時為了性能優(yōu)化,我們需要進行 mainScroll 事件的事件防抖
。
其中事件防抖工具函數(shù)---debounce.js
代碼如下:
/** * @param {Function} fn 需要進行防抖操作的事件函數(shù) * @param {Number} duration 間隔時間 * @returns {Function} 已進行防抖的函數(shù) */ export default function (fn, duration = 100) { let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn(...args); }, duration); }; }
圖片懶加載指令配置文件---lazy.js
該部分代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) // 調用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() {} //監(jiān)聽事件總線中的mainScroll事件,該事件觸發(fā)時調用setImages函數(shù)來加載圖片 eventBus.$on("mainScroll", debounce(setImages, 50));
3.2 使用自定義指令哪些鉤子函數(shù)?
經過場景分析,我選用了inserted
和unbind
這兩個鉤子函數(shù),當 img 元素剛插入父節(jié)點時收集 img 的信息,并在內部使用一個 imgs 數(shù)組存儲已收集到的信息,當指令與元素解綁時,進行 imgs 數(shù)組清空操作。
另外我們還需獲取圖片 img 元素的 DOM 節(jié)點和 src 屬性值
- 由于我們將指令綁定到了 img'元素上,所以可通過自定義指令鉤子函樹中的
el參數(shù)
得到其 DOM 節(jié)點 - 由于我們將 src 值傳給了指令,所以可通過
bindings.value參數(shù)
得到其 src 屬性值
所以此時圖片懶加載指令配置文件---lazy.js
該部分代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) // 調用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() {} //監(jiān)聽事件總線中的mainScroll事件,該事件觸發(fā)時調用setImages函數(shù)來加載圖片 eventBus.$on("mainScroll", debounce(setImages, 50)); //上面代碼是3.1 如何監(jiān)聽容器的滾動條的滾動? //下面代碼是3.2 使用自定義指令哪些鉤子函數(shù)? let imgs = []; //存儲收集到的的圖片信息 當圖片加載好后刪除該圖片信息 //調用setImage函數(shù),就可以進行單張圖片的加載 function setImage(img) {} export default { inserted(el, bindings) { //剛插入父節(jié)點時 收集img節(jié)點信息 const img = { dom: el, //img 元素DOM節(jié)點 src: bindings.value, //img的src屬性值 }; imgs.push(img); //先將圖片信息存儲到imgs數(shù)組 setImage(img); // 立即判斷該圖片是否要加載 }, unbind(el) { //解綁時 刪除 imgs 中的所有圖片信息 imgs = imgs.filter((img) => img.dom !== el); }, };
3.3 如何判斷圖片 img 元素是否在用戶的可見范圍內?
對于上面這問題,我們先進行問題拆分:
- 獲得用戶的可見范圍(視口)
由于我的博客系統(tǒng)只需考慮視口高度,所以我只使用了Element.clientHeight
這 API。(如果還需要考慮寬度就再使用Element.clientWidth)
- 獲得圖片 img 元素的位置信息
我使用了Element.getBoundingClientRect()
這 API。
- 判斷圖片 img 元素是否在視口內
img.getBoundingClientRect().top > 0 時,說明圖片在視口內或視口下方
- 當 img.getBoundingClientRect().top <= document.documentElement.clientHeight 時,該 img 元素在視口內
- 反之則不在視口內
img.getBoundingClientRect().top < 0 時,說明圖片在視口內或視口上方
- 當-img.getBoundingClientRect().top <= img.getBoundingClientRect().height 時,該 img 元素在視口內
- 反之則不在視口內
圖片懶加載指令配置文件---lazy.js
該部分代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) let imgs = []; //存儲收集到的的圖片信息 // 調用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() { for (const img of imgs) { setImage(img); // 處理該圖片 } } //監(jiān)聽事件總線中的mainScroll事件,該事件觸發(fā)時調用setImages函數(shù)來加載符合條件圖片 eventBus.$on("mainScroll", debounce(setImages, 50)); //當圖片加載好后刪除該圖片信息 export default { inserted(el, bindings) { //剛插入父節(jié)點時 收集img節(jié)點信息 const img = { dom: el, //img 元素DOM節(jié)點 src: bindings.value, //img的src屬性值 }; imgs.push(img); //先將圖片信息存儲到imgs數(shù)組 setImage(img); // 立即判斷該圖片是否要加載 }, unbind(el) { //解綁時 刪除 imgs 中的所有圖片信息 imgs = imgs.filter((img) => img.dom !== el); }, }; //上面代碼是3.1 如何監(jiān)聽容器的滾動條的滾動?+ 3.2 使用自定義指令哪些鉤子函數(shù)? //下面代碼是3.3 如何判斷圖片 img 元素是否在用戶的可見范圍內? //調用setImage函數(shù),就可以進行單張圖片的加載 function setImage(img) { const clientHeight = document.documentElement.clientHeight; //視口高度 const rect = img.dom.getBoundingClientRect(); //圖片的位置信息 //取默認值150 是為了解決圖片未加載成功時高度缺失的問題 const height = rect.height || 150; //圖片的高度 // 判斷該圖片是否在視口范圍內 if (rect.top >= -height && rect.top <= clientHeight) { // 在視口范圍內 進行相關處理操作 } else { // 不在視口范圍內 不進行操作 } }
3.4 如何處理圖片 img 元素的加載?
由效果圖我們可看出一開始所有 img 元素都是一張默認的 GIF 圖片---defaultGif
,等該 img 元素進入到視口范圍時,開始加載該圖片,加載完成后再進行替換。
這里我還進行一個優(yōu)化操作,就是先新建一個 Image 對象實例
,代替 img 元素加載圖片,因為圖片加載完成后會觸發(fā)onload事件
,所以我們只需對onload事件
進行改寫,在其內部執(zhí)行 img 元素的 src 屬性替換操作,這樣就解決了加載過程中圖片空白的情況。
所以圖片懶加載指令配置文件---lazy.js
完整的代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) import defaultGif from "@/assets/default.gif"; //在assets靜態(tài)文件夾下放入默認圖 let imgs = []; //存儲收集到的且未加載的圖片信息 //調用setImage函數(shù),就可以進行單張圖片的加載 function setImage(img) { img.dom.src = defaultGif; // 先暫時使用默認圖片 const clientHeight = document.documentElement.clientHeight; //視口高度 const rect = img.dom.getBoundingClientRect(); //圖片的位置信息 //取默認值150 是為了解決圖片未加載成功時 高度缺失的問題 const height = rect.height || 150; //圖片的高度 // 判斷該圖片是否在視口范圍內 if (-rect.top <= height && rect.top <= clientHeight) { // 在視口范圍內 進行相關處理操作 const tempImg = new Image(); //新建Image對象實例 //改寫onload事件 tempImg.onload = function () { // 當圖片加載完成之后 img.dom.src = img.src; //替換img元素的src屬性 }; tempImg.src = img.src; imgs = imgs.filter((i) => i !== img); //將已加載好的圖片進行刪除 } } // 調用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() { for (const img of imgs) { setImage(img); // 處理該圖片 } } //監(jiān)聽事件總線中的mainScroll事件,該事件觸發(fā)時調用setImages函數(shù)來加載符合條件圖片 eventBus.$on("mainScroll", debounce(setImages, 50)); //當圖片加載好后刪除該圖片信息 export default { inserted(el, bindings) { //剛插入父節(jié)點時 收集img節(jié)點信息 const img = { dom: el, //img 元素DOM節(jié)點 src: bindings.value, //img的src屬性值 }; imgs.push(img); //先將圖片信息存儲到imgs數(shù)組 setImage(img); // 立即判斷該圖片是否要加載 }, unbind(el) { //解綁時 清空 imgs imgs = imgs.filter((img) => img.dom !== el); }, };
以上就是Vue2 中自定義圖片懶加載指令 v-lazy實例詳解的詳細內容,更多關于Vue2自定義v-lazy指令的資料請關注腳本之家其它相關文章!
相關文章
vue+bpmn.js實現(xiàn)自定義流程圖的完整代碼
這篇文章主要介紹了vue+bpmn.js實現(xiàn)自定義流程圖的完整代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借價值,需要的朋友參考下吧2024-03-03vue級聯(lián)選擇器的getCheckedNodes使用方式
這篇文章主要介紹了vue級聯(lián)選擇器的getCheckedNodes使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04詳解vuex中action何時完成以及如何正確調用dispatch的思考
這篇文章主要介紹了詳解vuex中action何時完成以及如何正確調用dispatch的思考,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01element的el-form和el-table嵌套使用實現(xiàn)
本文主要介紹了element的el-form和el-table嵌套使用實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08