這篇文章主要講解了“怎么在Vue2中自定義一個(gè)圖片懶加載指令”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“怎么在Vue2中自定義一個(gè)圖片懶加載指令”吧!
創(chuàng)新互聯(lián)主要從事成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)黎平,十載網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來(lái)電咨詢建站服務(wù):13518219792
自定義圖片懶加載指令主要涉及以下三塊知識(shí):
Vue2 中自定義指令
使用事件總線進(jìn)行模塊之間的通信
使用到的 Web API
Element.clientHeight
Element.getBoundingClientRect()
下面我會(huì)對(duì)這些知識(shí)點(diǎn)進(jìn)行一一介紹。
下面我只對(duì)自定義指令做簡(jiǎn)單的介紹,詳細(xì)介紹大家可以參照Vue 官網(wǎng) - 自定義指令。
bind:只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置。
inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)。
update:所在組件的 VNode 更新時(shí)調(diào)用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒(méi)有??赏ㄟ^(guò)比較更新前后的值來(lái)忽略不必要的模板更新 (詳細(xì)的鉤子函數(shù)參數(shù)見(jiàn)下)。
componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用。
鉤子函數(shù)的參數(shù)主要有這四個(gè)el、binding、vnode、oldVnode
。
el:指令所綁定的元素,可以用來(lái)直接操作 DOM。
binding:一個(gè)對(duì)象,包含以下 property:
name:指令名,不包括 v- 前綴。
value:指令的綁定值,如:v-my-directive="1 + 1" 中,綁定值為 2。
oldValue:指令綁定的前一個(gè)值,僅在 update 和 componentUpdated 鉤子中可用。無(wú)論值是否改變都可用。
expression:字符串形式的指令表達(dá)式。如 v-my-directive="1 + 1" 中,表達(dá)式為 "1 + 1"。
arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。
modifiers:一個(gè)包含修飾符的對(duì)象。例如:v-my-directive.foo.bar 中,修飾符對(duì)象為 { foo: true, bar: true }。
vnode:Vue 編譯生成的虛擬節(jié)點(diǎn)。移步 VNode API 來(lái)了解更多詳情。
oldVnode:上一個(gè)虛擬節(jié)點(diǎn),僅在 update 和 componentUpdated 鉤子中可用。
對(duì)事件總線不熟悉的朋友,可以參照該博客什么是 Vue 事件總線(EventBus)。
監(jiān)聽(tīng)事件總線上的事件---調(diào)用 $on 方法
觸發(fā)事件總線上的事件---調(diào)用 $emit 方法
取消監(jiān)聽(tīng)事件總線上的事件---調(diào)用 $off 方法
我們可以借助 vue 示例來(lái)實(shí)現(xiàn)事件總線,也可以自行封裝;我使用了第一種方法。
因此事件總線配置文件---eventBus.js
的代碼如下:
import Vue from "vue"; const eventBus = new Vue({}); /* * 事件名:mainScroll * 含義:主區(qū)域滾動(dòng)條位置變化后觸發(fā) * 參數(shù): * - 滾動(dòng)的dom元素,如果是undefined,則表示dom元素已經(jīng)不存在 */ //在Vue.prototype原型上注冊(cè)事件總線,方便vue實(shí)例對(duì)象監(jiān)聽(tīng)和觸發(fā) Vue.prototype.$bus = eventBus; //導(dǎo)出事件總線,方便在其他js模塊監(jiān)聽(tīng)和觸發(fā)事件總線上的事件 export default eventBus;
首先Element.clientHeight
是一個(gè)只讀屬性,具有以下特點(diǎn):
對(duì)于那些沒(méi)有定義 CSS 或者內(nèi)聯(lián)布局盒子的元素,該 API 會(huì)返回 0;
對(duì)于根元素(html 元素)或怪異模式下的 body 元素,該 API 將返回視口高度(不包含任何滾動(dòng)條)
其他情況,該 API 會(huì)返回元素內(nèi)部的高度(以像素為單位),包含content
與padding
,不包含border
、margin
與水平滾動(dòng)條(如果存在)。
另外改 API 會(huì)將獲取的值四舍五入取整數(shù)。如果你需要小數(shù)結(jié)果,可以使用 element.getBoundingClientRect()方法。
示例圖如下:
Element.getBoundingClientRect()
方法返回一個(gè)DOMRect
對(duì)象,其提供了元素的大小及其相對(duì)于視口的位置。
該方法無(wú)參數(shù),返回值為DOMRect
對(duì)象,該對(duì)象的屬性以下幾個(gè):
width:就是元素自身寬度
height: 元素自身高度
left(x):元素開(kāi)始位置到窗口左邊的距離
right: 元素的右邊到窗口左邊的距離
bottom: 元素的下邊到窗口上邊的距離
top(y): 元素的上邊到窗口上邊的距離
x 和 y 相當(dāng)于 left 和 top
示意圖如下:
該 API 的詳細(xì)文檔可以參照MDN - Element.getBoundingClientRect()
由于在個(gè)人博客系統(tǒng)中圖片懶加載指令使用的比較頻繁,使用我選擇了全局注冊(cè)該指令。
另外因?yàn)槲沂褂檬录偩€這方法來(lái)自己通信,使用還需引入事件總線配置文件---eventBus.js
所以 main.js入口文件
的代碼如下:
import Vue from "vue"; import App from "./App.vue"; import "./eventBus"; //引入事件總線 import vLazy from "./directives/lazy"; Vue.directive("lazy", vLazy); //全局注冊(cè)指令 new Vue({ render: (h) => h(App), }).$mount("#app");
使用 v-lazy 指令的示例代碼如下:
要實(shí)現(xiàn)圖片懶加載效果,我們首先要思考以下四個(gè)關(guān)鍵問(wèn)題:
如何監(jiān)聽(tīng)容器的滾動(dòng)條的滾動(dòng)?
使用自定義指令哪些鉤子函數(shù)?
如何判斷圖片 img 元素是否在用戶的可見(jiàn)范圍內(nèi)?
如何處理圖片 img 元素的加載?
對(duì)于這問(wèn)題,由于我的博客系統(tǒng)在處理其他組件之間的傳值問(wèn)題時(shí),使用了事件總線方法,所以為了方便,我也使用這一方法,當(dāng)然大家可以針對(duì)實(shí)際場(chǎng)景使用其他方法來(lái)解決這問(wèn)題。
所以我們要在 v-lazy 圖片懶加載指令配置文件---lazy.js
文件中監(jiān)聽(tīng)事件總線 eventBus 中的mainScroll事件
,同時(shí)為了性能優(yōu)化,我們需要進(jìn)行 mainScroll 事件的事件防抖
。
其中事件防抖工具函數(shù)---debounce.js
代碼如下:
/** * @param {Function} fn 需要進(jìn)行防抖操作的事件函數(shù) * @param {Number} duration 間隔時(shí)間 * @returns {Function} 已進(jìn)行防抖的函數(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ù) // 調(diào)用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() {} //監(jiān)聽(tīng)事件總線中的mainScroll事件,該事件觸發(fā)時(shí)調(diào)用setImages函數(shù)來(lái)加載圖片 eventBus.$on("mainScroll", debounce(setImages, 50));
經(jīng)過(guò)場(chǎng)景分析,我選用了inserted
和unbind
這兩個(gè)鉤子函數(shù),當(dāng) img 元素剛插入父節(jié)點(diǎn)時(shí)收集 img 的信息,并在內(nèi)部使用一個(gè) imgs 數(shù)組存儲(chǔ)已收集到的信息,當(dāng)指令與元素解綁時(shí),進(jìn)行 imgs 數(shù)組清空操作。
另外我們還需獲取圖片 img 元素的 DOM 節(jié)點(diǎn)和 src 屬性值
由于我們將指令綁定到了 img'元素上,所以可通過(guò)自定義指令鉤子函樹(shù)中的el參數(shù)
得到其 DOM 節(jié)點(diǎn)
由于我們將 src 值傳給了指令,所以可通過(guò)bindings.value參數(shù)
得到其 src 屬性值
所以此時(shí)圖片懶加載指令配置文件---lazy.js
該部分代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) // 調(diào)用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() {} //監(jiān)聽(tīng)事件總線中的mainScroll事件,該事件觸發(fā)時(shí)調(diào)用setImages函數(shù)來(lái)加載圖片 eventBus.$on("mainScroll", debounce(setImages, 50)); //上面代碼是3.1 如何監(jiān)聽(tīng)容器的滾動(dòng)條的滾動(dòng)? //下面代碼是3.2 使用自定義指令哪些鉤子函數(shù)? let imgs = []; //存儲(chǔ)收集到的的圖片信息 當(dāng)圖片加載好后刪除該圖片信息 //調(diào)用setImage函數(shù),就可以進(jìn)行單張圖片的加載 function setImage(img) {} export default { inserted(el, bindings) { //剛插入父節(jié)點(diǎn)時(shí) 收集img節(jié)點(diǎn)信息 const img = { dom: el, //img 元素DOM節(jié)點(diǎn) src: bindings.value, //img的src屬性值 }; imgs.push(img); //先將圖片信息存儲(chǔ)到imgs數(shù)組 setImage(img); // 立即判斷該圖片是否要加載 }, unbind(el) { //解綁時(shí) 刪除 imgs 中的所有圖片信息 imgs = imgs.filter((img) => img.dom !== el); }, };
對(duì)于上面這問(wèn)題,我們先進(jìn)行問(wèn)題拆分:
1、獲得用戶的可見(jiàn)范圍(視口)
由于我的博客系統(tǒng)只需考慮視口高度,所以我只使用了Element.clientHeight
這 API。(如果還需要考慮寬度就再使用Element.clientWidth)
2、獲得圖片 img 元素的位置信息
我使用了Element.getBoundingClientRect()
這 API。
3、判斷圖片 img 元素是否在視口內(nèi)
img.getBoundingClientRect().top > 0 時(shí),說(shuō)明圖片在視口內(nèi)或視口下方
當(dāng) img.getBoundingClientRect().top <= document.documentElement.clientHeight 時(shí),該 img 元素在視口內(nèi)
反之則不在視口內(nèi)
img.getBoundingClientRect().top < 0 時(shí),說(shuō)明圖片在視口內(nèi)或視口上方
當(dāng)-img.getBoundingClientRect().top <= img.getBoundingClientRect().height 時(shí),該 img 元素在視口內(nèi)
反之則不在視口內(nèi)
圖片懶加載指令配置文件---lazy.js
該部分代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) let imgs = []; //存儲(chǔ)收集到的的圖片信息 // 調(diào)用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() { for (const img of imgs) { setImage(img); // 處理該圖片 } } //監(jiān)聽(tīng)事件總線中的mainScroll事件,該事件觸發(fā)時(shí)調(diào)用setImages函數(shù)來(lái)加載符合條件圖片 eventBus.$on("mainScroll", debounce(setImages, 50)); //當(dāng)圖片加載好后刪除該圖片信息 export default { inserted(el, bindings) { //剛插入父節(jié)點(diǎn)時(shí) 收集img節(jié)點(diǎn)信息 const img = { dom: el, //img 元素DOM節(jié)點(diǎn) src: bindings.value, //img的src屬性值 }; imgs.push(img); //先將圖片信息存儲(chǔ)到imgs數(shù)組 setImage(img); // 立即判斷該圖片是否要加載 }, unbind(el) { //解綁時(shí) 刪除 imgs 中的所有圖片信息 imgs = imgs.filter((img) => img.dom !== el); }, }; //上面代碼是3.1 如何監(jiān)聽(tīng)容器的滾動(dòng)條的滾動(dòng)?+ 3.2 使用自定義指令哪些鉤子函數(shù)? //下面代碼是3.3 如何判斷圖片 img 元素是否在用戶的可見(jiàn)范圍內(nèi)? //調(diào)用setImage函數(shù),就可以進(jìn)行單張圖片的加載 function setImage(img) { const clientHeight = document.documentElement.clientHeight; //視口高度 const rect = img.dom.getBoundingClientRect(); //圖片的位置信息 //取默認(rèn)值150 是為了解決圖片未加載成功時(shí)高度缺失的問(wèn)題 const height = rect.height || 150; //圖片的高度 // 判斷該圖片是否在視口范圍內(nèi) if (rect.top >= -height && rect.top <= clientHeight) { // 在視口范圍內(nèi) 進(jìn)行相關(guān)處理操作 } else { // 不在視口范圍內(nèi) 不進(jìn)行操作 } }
由效果圖我們可看出一開(kāi)始所有 img 元素都是一張默認(rèn)的 GIF 圖片---defaultGif
,等該 img 元素進(jìn)入到視口范圍時(shí),開(kāi)始加載該圖片,加載完成后再進(jìn)行替換。
這里我還進(jìn)行一個(gè)優(yōu)化操作,就是先新建一個(gè) Image 對(duì)象實(shí)例
,代替 img 元素加載圖片,因?yàn)閳D片加載完成后會(huì)觸發(fā)onload事件
,所以我們只需對(duì)onload事件
進(jìn)行改寫,在其內(nèi)部執(zhí)行 img 元素的 src 屬性替換操作,這樣就解決了加載過(guò)程中圖片空白的情況。
所以圖片懶加載指令配置文件---lazy.js
完整的代碼如下:
import eventBus from "@/eventBus"; //引入事件總線 import { debounce } from "@/utils"; //引入函數(shù)防抖工具函數(shù) import defaultGif from "@/assets/default.gif"; //在assets靜態(tài)文件夾下放入默認(rèn)圖 let imgs = []; //存儲(chǔ)收集到的且未加載的圖片信息 //調(diào)用setImage函數(shù),就可以進(jìn)行單張圖片的加載 function setImage(img) { img.dom.src = defaultGif; // 先暫時(shí)使用默認(rèn)圖片 const clientHeight = document.documentElement.clientHeight; //視口高度 const rect = img.dom.getBoundingClientRect(); //圖片的位置信息 //取默認(rèn)值150 是為了解決圖片未加載成功時(shí) 高度缺失的問(wèn)題 const height = rect.height || 150; //圖片的高度 // 判斷該圖片是否在視口范圍內(nèi) if (-rect.top <= height && rect.top <= clientHeight) { // 在視口范圍內(nèi) 進(jìn)行相關(guān)處理操作 const tempImg = new Image(); //新建Image對(duì)象實(shí)例 //改寫onload事件 tempImg.onload = function () { // 當(dāng)圖片加載完成之后 img.dom.src = img.src; //替換img元素的src屬性 }; tempImg.src = img.src; imgs = imgs.filter((i) => i !== img); //將已加載好的圖片進(jìn)行刪除 } } // 調(diào)用setImages函數(shù),就可以處理那些符合條件的圖片 function setImages() { for (const img of imgs) { setImage(img); // 處理該圖片 } } //監(jiān)聽(tīng)事件總線中的mainScroll事件,該事件觸發(fā)時(shí)調(diào)用setImages函數(shù)來(lái)加載符合條件圖片 eventBus.$on("mainScroll", debounce(setImages, 50)); //當(dāng)圖片加載好后刪除該圖片信息 export default { inserted(el, bindings) { //剛插入父節(jié)點(diǎn)時(shí) 收集img節(jié)點(diǎn)信息 const img = { dom: el, //img 元素DOM節(jié)點(diǎn) src: bindings.value, //img的src屬性值 }; imgs.push(img); //先將圖片信息存儲(chǔ)到imgs數(shù)組 setImage(img); // 立即判斷該圖片是否要加載 }, unbind(el) { //解綁時(shí) 清空 imgs imgs = imgs.filter((img) => img.dom !== el); }, };
感謝各位的閱讀,以上就是“怎么在Vue2中自定義一個(gè)圖片懶加載指令”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)怎么在Vue2中自定義一個(gè)圖片懶加載指令這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!