Vue圖片懶加載之Vue-Lazyload的使用
一、什么叫懶加載
通俗講 : 懶加載就是延時(shí)加載,即當(dāng)需要用到的時(shí)候再去加載。
那什么叫做需要用到的時(shí)候?比如一個(gè)圖片在沒有出現(xiàn)在可視區(qū)域內(nèi),就已經(jīng)加載當(dāng)頁面里了, 但只有滾動(dòng)頁面下方式才能看見, 則可以認(rèn)為這個(gè)圖片加載的"過早"了。
二、懶加載的優(yōu)點(diǎn)
- 可以減少首頁首次加載的數(shù)量,減少服務(wù)器的壓力
- 當(dāng)網(wǎng)絡(luò)請求比較慢的時(shí)候, 提前給這張圖片添加一個(gè)像素比較低的占位圖片,不至于堆疊在一塊,或顯示大片空白,讓用戶體驗(yàn)更好一點(diǎn)。
三、為什么使用懶加載
可以想象一個(gè)網(wǎng)頁打開有成百上千的圖片需要加載,頁面會(huì)變得非常的卡頓,此時(shí)如果只是可視區(qū)域的圖片加載,其他的圖片可以暫時(shí)有一個(gè)占位 loading 圖,等滾動(dòng)它們到可視區(qū)域時(shí)再去請求真實(shí)圖片并且替換就好了。vue-lazyload 插件就是解決此類問題的,對vue插件的寫法不熟悉的可以先看一下vue插件
懶加載原理是什么
頁面中的img元素,如果沒有src屬性,瀏覽器就不會(huì)發(fā)出請求去下載圖片,只有通過javascript設(shè)置了圖片路徑,瀏覽器才會(huì)發(fā)送請求。
懶加載的原理就是先在頁面中把所有的圖片統(tǒng)一使用一張占位圖進(jìn)行占位,把真正的路徑存在元素的“data-url”(這個(gè)名字起個(gè)自己認(rèn)識好記的就行)屬性里,當(dāng)js監(jiān)聽到該圖片元素進(jìn)入可視窗口時(shí),即將自定義屬性中的地址存儲(chǔ)到src屬性中,達(dá)到懶加載的效果。
四、vue中如何實(shí)現(xiàn)懶加載
第一步: 安裝
npm install vue-lazyload --save
第二步: 全局注冊(main.js)
// main.js 文件
import VueLazyload from 'vue-lazyload'
// Vue.use(VueLazyload) //無配置項(xiàng)
// 配置項(xiàng)
const loadimage = require('assets/img/common/loading.gif')
// const errorimage = require('assets/img/common/error.gif')
Vue.use(VueLazyload, {
preLoad: 1.3, //預(yù)加載的寬高比
loading: loadimage, //圖片加載狀態(tài)下顯示的圖片
// error: errorimage, //圖片加載失敗時(shí)顯示的圖片
attempt: 1, // 加載錯(cuò)誤后最大嘗試次數(shù)
})
// img元素上使用v-lazy="src";<img v-lazy="showImage"/>配置項(xiàng)的參數(shù)說明
| 鍵 | 描述 | 默認(rèn) | 選項(xiàng) |
|---|---|---|---|
| preLoad | 表示lazyload的元素, 距離頁面底部距離的百分比. 計(jì)算值為(preload - 1) | 1.3 | Number |
| error | 加載失敗后圖片地址 | 'data-src' | String |
| loading | 加載時(shí)圖片地址 | 'data-src' | String |
| attempt | 圖片加載失敗后的重試次數(shù) | 3 | Number |
| listenEvents | 觸發(fā)懶加載的事件 | ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove'] | 無 |
| adapter | 注冊img 的loading,loaded,error 三個(gè)狀態(tài)的回調(diào)函數(shù), 參數(shù)會(huì)暴露懶加載的img元素, 可以對其進(jìn)行操作. | { } | 無 |
| filter | img未加載之前, 解析到src 的時(shí)候注冊的回調(diào)函數(shù). 可以在加載圖片之前,對src進(jìn)行修改. 注冊在filter下的所有的函數(shù)都會(huì)執(zhí)行 | { } | 無 |
| lazyComponent | 是否啟用懶加載組件. <lazy-component>組件中的內(nèi)容 只有在出現(xiàn)在preload的 位置中才會(huì)加載組件. 這個(gè)lazyloadComponent 組件有個(gè)缺點(diǎn) 就是,組件在加載前 是什么都不渲染的, 這樣子的話,有可能會(huì)影響布局, 以及加載前到加載后的切換不好, 有點(diǎn)突兀和生硬. | false | 無 |
| dispatchEvent | 觸發(fā)dom事件 | false | Boolean |
| throttleWait | 等待時(shí)長 | 200 | Number |
| observer | 是否啟用IntersectionObserver, 這個(gè)api有兼容問題 | false | Boolean |
| observerOptions | IntersectionObserver選項(xiàng) | { rootMargin: '0px', threshold: 0.1 } | 無 |
| silent | 不打印調(diào)試信息 | true | Boolean |
因?yàn)閟rc中的文件會(huì)被webpack編譯,assets文件夾中的圖片地址,會(huì)在編譯過程中重命名。vue-lazyload是在main.js文件中引入,不會(huì)被webpack進(jìn)行編譯,因此vue-lazyload無法獲得正確的圖片地址,所以直接寫相對地址就無法獲取到圖片正確地址
第三步: 寫loading圖片的樣式(不是必須, 視情況而定)
img[lazy="loading"]{
display:block;
width:50px !important;
height:50px !important;
margin:0 auto;
}第四步: 使用 ( :src--->v-lazy )
<div class="lazyLoad">
<ul>
<li v-for="img in arr">
<img v-lazy="img.thumbnail_pic_s">
</li>
</ul>
</div>
這里有個(gè)坑需要注意如設(shè)置了翻頁功能,且每一頁都是請求的數(shù)據(jù)進(jìn)行渲染。
會(huì)發(fā)現(xiàn)其他的數(shù)據(jù)都變了,唯獨(dú)圖片還是原來的圖片。
由于使用的數(shù)據(jù)是父組件傳過來的,第一個(gè)想到父組件axios異步請求的數(shù)據(jù)導(dǎo)致子組件可能數(shù)據(jù)沒有動(dòng)態(tài)更新。但監(jiān)聽了下數(shù)據(jù),發(fā)現(xiàn)確實(shí)是改變了 .
解決辦法只要加個(gè)key就行, 如下代碼
<ul>
<li v-for="img in list">
<img v-lazy="img.src" :key="img.src" >
</li>
</ul>
五、js---懶加載的實(shí)現(xiàn)步驟?
1)首先,不要將圖片地址放到src屬性中,而是放到其它屬性(data-original)中。
2)頁面加載完成后,根據(jù)scrollTop判斷圖片是否在用戶的視野內(nèi),如果在,則將data-original屬性中的值取出存放到src屬性中。
3)在滾動(dòng)事件中重復(fù)判斷圖片是否進(jìn)入視野,如果進(jìn)入,則將data-original屬性中的值取出存放到src屬性中。
懶加載代碼實(shí)現(xiàn)
方式一:原生js
元素距頂部的高度 - 頁面被卷去的高度 <= 瀏覽器可視區(qū)的高度)
來判斷是否符合我們想要的條件.需要實(shí)時(shí)監(jiān)聽頁面滾動(dòng)時(shí) 圖片的高度變化

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
}
img {
width: 400px;
height: 300px;
}
</style>
<script>
window.onload = function () {
var imgs = document.querySelectorAll("img");
// 初始化執(zhí)行
lazyLoad(imgs);
// 滾動(dòng)執(zhí)行
window.addEventListener("scroll", function () {
lazyLoad(imgs);
});
function lazyLoad(imgs) {
for (let i = 0; i < imgs.length; i++) {
var imgoffsetT = imgs[i].offsetTop; // 圖片的距頂部的高度
var wheight = window.innerHeight; // 瀏覽器可視區(qū)的高度
var scrollT = document.documentElement.scrollTop; // 頁面被卷去的高度
if (imgoffsetT - scrollT <= wheight) {
// 判斷圖片是否將要出現(xiàn)
imgs[i].src = imgs[i].dataset.src; // 出現(xiàn)后將自定義地址轉(zhuǎn)為真實(shí)地址
}
}
}
};
/*
obj.getAttribute("屬性名")
通過元素節(jié)點(diǎn)的屬性名稱獲取屬性的值。
使用data-前綴設(shè)置我們需要的自定義屬性,來進(jìn)行一些數(shù)據(jù)的存放,
dataset 獲取自定義屬性值的使用
*/
</script>
</head>
<body>
<ul>
<li>
<img data-src="./img/img1.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img2.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img3.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img4.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img5.png" src="./img/loading.gif" alt="" />
</li>
</ul>
</body>
</html>方式二: getBoundingClientRect()

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
}
img {
width: 400px;
height: 300px;
}
</style>
<script>
window.onload = function () {
var imgs = document.querySelectorAll("img");
// 初始調(diào)動(dòng)一次
lazyLoad();
window.addEventListener("scroll", throttle(lazyLoad, 1000), false);
//函數(shù)1:封裝判定圖片是否在可視區(qū)
function isInVisibleArea(imgOne) {
const info = imgOne.getBoundingClientRect();
// 獲取頁面可視區(qū)的高度,寬度
let windowH = window.innerHeight;
let windowW = window.innerWidth;
// 限定參數(shù)在可視區(qū)內(nèi)
let res = info.bottom > 0 && info.top < windowH && info.right > 0 && info.left < windowW;
return res;
}
//函數(shù)2: 封裝滾動(dòng)時(shí)重新加載函數(shù)
function lazyLoad() {
for (let i = 0; i < imgs.length; i++) {
const imgOne = imgs[i];
// 判定是否在可視區(qū)內(nèi)
if (isInVisibleArea(imgOne)) {
// 替換src方法一:
// imgOne.src = imgOne.getAttribute("data-src");
// 替換src方法二:
imgOne.src = imgOne.dataset.src;
// imgs.splice(i,1)
// i--;
}
console.log("我滾了"); //所以要做節(jié)流操作
}
}
//函數(shù)3:節(jié)流函數(shù)
/*
參數(shù)1:函數(shù)
參數(shù)2:執(zhí)行時(shí)間
*/
function throttle(fn, time = 250) {
let lastTime = null;
return function (...args) {
const now = Date.now(); //當(dāng)前時(shí)間
if (now - lastTime >= time) {
fn();//幫助執(zhí)行函數(shù),改變上下文
lastTime = now;
}
};
}
};
/*
getBoundingClientRect()
——獲取元素位置,這個(gè)方法沒有參數(shù)
——用于獲得頁面中某個(gè)元素的左,上,右和下分別相對瀏覽器視窗的位置。
——是DOM元素到瀏覽器可視范圍的距離(不包含文檔卷起的部分)。
該函數(shù)返回一個(gè)Object對象,該對象有6個(gè)屬性:top,lef,right,bottom,width,height;
*/
</script>
</head>
<body>
<ul>
<li>
<img data-src="./img/img1.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img2.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img3.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img4.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img5.png" src="./img/loading.gif" alt="" />
</li>
</ul>
</body>
</html>上面的方法需要頻繁觸發(fā) scroll 事件,很容易造成卡頓或者頁面性能問題。
方式三:IntersectionObserver(callback)
1.概念
IntersectionObserve 是瀏覽器提供的一個(gè)原生構(gòu)造函數(shù),它也被稱作交叉觀察器。 它可以觀察我們的元素是否可見,也就是是否和可視區(qū)發(fā)生交叉。官網(wǎng)的解釋:IntersectionObserver 接口提供了一種異步觀察目標(biāo)元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態(tài)的方法。祖先元素與視窗(viewport)被稱為根(root)。
官網(wǎng)說的稍微晦澀一點(diǎn),我們通俗的給大家解釋一下,結(jié)合一張圖應(yīng)該就很好里面了。
通俗的解釋:我們可以使用 IntersectionObserver 接口觀察一個(gè)元素,觀察它是否進(jìn)入了可視區(qū),這個(gè)可視區(qū)可以相對于視窗或者祖先元素。

上面的圖就很形象的描述了一個(gè)元素逐步出現(xiàn)在可視區(qū)內(nèi)的過程,當(dāng)元素和可視區(qū)發(fā)生交叉時(shí),則代表進(jìn)入可視區(qū)內(nèi)了。而我們的 “交叉觀察器” IntersectionObserve 就和名字一樣,專門用來觀察何時(shí)交叉。
2.基本使用
IntersectionObserve 使用起來很簡單,我們了解了它接收的參數(shù)以及攜帶的方法如何使用后,便可以很快的上手。
2.1 初始化實(shí)例
因?yàn)樗且粋€(gè)構(gòu)造函數(shù),所以我們可以使用 new 的方式實(shí)例化它,代碼如下:
<script> let IO = new IntersectionObserver(callback, options); </script>
該構(gòu)造函數(shù)接收兩個(gè)參數(shù):
- callback:回調(diào)函數(shù),當(dāng)元素的可見性發(fā)生變化,即元素與目標(biāo)元素相交發(fā)生改變時(shí)會(huì)觸發(fā)該回調(diào)函數(shù)。
- options:一些配置項(xiàng)參數(shù),如果不傳會(huì)有默認(rèn)值,它可以用來配置可視區(qū)元素、什么時(shí)候觸發(fā)回調(diào)等等,默認(rèn)就是瀏覽器視口。
2.2 回調(diào)函數(shù)參數(shù)callback 會(huì)接收兩個(gè)參數(shù)
主要解釋如下:
entries:它是一個(gè) IntersectionObserverEntry 對象數(shù)組 ,IntersectionObserverEntry 主要存儲(chǔ)的是一些觀察元素的信息,主要有以下 7 個(gè)屬性:
- time:可見性發(fā)生變化的時(shí)間,是一個(gè)高精度時(shí)間戳,單位為毫秒
- target:被觀察的目標(biāo)元素,是一個(gè) DOM 節(jié)點(diǎn)對象
- rootBounds:根元素的矩形區(qū)域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對于視口滾動(dòng)),則返回 null
- boundingClientRect:目標(biāo)元素的矩形區(qū)域的信息
- isIntersecting:目標(biāo)元素當(dāng)前是否可見 Boolean 值 可見為 true
- intersectionRect:目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息
- intersectionRatio:目標(biāo)元素的可見比例,即 intersectionRect 占 boundingClientRect 的比例,完全可見時(shí)為 1,完全不可見時(shí)小于等于 0
observer:它返回的是被調(diào)用的 IntersectionObserve 實(shí)例,我們通常無需操作。
2.3 options 配置options 是構(gòu)造函數(shù)的第二個(gè)參數(shù),是一個(gè)對象的形式,它主要一些配置信息,主要配置項(xiàng)有如下幾個(gè):
- root:主要用來配置被觀察元素是相對于誰可見和不可見,如果不配置,則默認(rèn)的是瀏覽器視口。
- threshold:主要用來配置兩個(gè)元素的交叉比例,它是一個(gè)數(shù)組,用于決定在什么時(shí)候觸發(fā)回調(diào)函數(shù)。
- rootMargin:用來改變可視區(qū)域的范圍,假如我們可視區(qū)域大小是 300x300,可以通過該參數(shù)改變可視區(qū)域大小,但是實(shí)際像素值并沒有變,優(yōu)點(diǎn)類似于我們上拉加載更多場景:當(dāng)距離底部多少多少像素的時(shí)候就加載。
看圖理解:

let viewport = document.getElementById("viewport"); // 可視區(qū)域
let options = {
root: viewport,
threshold: [0, 0.5, 1],
rootMargin: '30px 100px 20px'
}2.4 實(shí)例方法
初始化實(shí)例后,我們就可以調(diào)用實(shí)例方法了。IntersectionObserver 實(shí)例常用的方法常主要有下面幾個(gè):
- IO.observe([element]):使用該方法后代表我們開始觀察某個(gè)元素了,它接收一個(gè)元素節(jié)點(diǎn)作為參數(shù),也就是被觀察元素。
- IO.unobserve([element]):該方法用于停止觀察某元素,同樣接收一個(gè)元素節(jié)點(diǎn)作為參數(shù)。
- IO.disconnect():該方法用于關(guān)閉觀察器。
可以先簡單演示一下,看看何時(shí)觸發(fā) callback。
3.代碼演示
3.1 查看 entries 和 observe
我們先來看一下回調(diào)函數(shù)里面默認(rèn)傳遞的參數(shù)打印出來是什么:entries 和 observe。
示例代碼:
<head>
<style>
.viewport {
width: 300px;
height: 200px;
border: 1px solid blue;
overflow: auto;
}
.box1 {
height: 600px;
width: 100%;
}
.observed {
width: 100px;
height: 100px;
border: 1px solid green;
}
</style>
</head>
<body>
<div class="viewport" id="viewport">
<div class="box1">
<div class="observed" id="observed"></div>
</div>
</div>
</body>
<script>
let viewport = document.getElementById("viewport"); // 可視區(qū)域
let observed = document.getElementById("observed"); // 被觀察元素
let options = {
root: viewport, // 指定可視區(qū)元素
}
let IO = new IntersectionObserver(IOCallback, options); // 初始化實(shí)例
IO.observe(observed); // 開始觀察
// 回調(diào)函數(shù)
function IOCallback(entries, observer) {
console.info("entries", entries);
console.info("observer", observer);
}
</script>
輸出結(jié)果:

這里的代碼還比較簡單,我們這里設(shè)置了視圖窗口為我們指定的 id 為 viewport 的元素,被觀察元素為 id 為 observed 的元素。當(dāng)我們刷新頁面的時(shí)候,IOCallback 回調(diào)函數(shù)便會(huì)執(zhí)行,且打印了 entries 和 observe,至于它們中每個(gè)參數(shù)代表的意義大家可以參照上一節(jié)。

3.2 實(shí)現(xiàn)圖片懶加載
圖片懶加載是我們非常常見的一個(gè)場景了,這里我們拿這個(gè)場景距離相信大家可以更加容易理解。
需求背景:我們有非常多的圖片,如果一次性全部渲染,非常消耗性能。所以我們需要實(shí)現(xiàn)圖片出現(xiàn)在可視區(qū)域內(nèi)后在進(jìn)行渲染加載。
實(shí)現(xiàn)思路:
- 先確定可視區(qū)窗口
- 為所有 img 標(biāo)簽添加一個(gè)自定義 data-src 屬性,用來存放圖片真正路徑
- 利用 IntersectionObserve 觀察每一張圖片是否進(jìn)入可視區(qū)內(nèi)
- 如果進(jìn)入可視區(qū)內(nèi),則將圖片的 src 路徑替換為真正的 data-src 路徑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
}
img {
width: 400px;
height: 300px;
}
</style>
</head>
<body>
<ul id="view">
<li>
<img data-src="./img/img1.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img2.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img3.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img4.gif" src="./img/loading.gif" alt="" />
</li>
<li>
<img data-src="./img/img5.png" src="./img/loading.gif" alt="" />
</li>
</ul>
<script>
const imgs = document.querySelectorAll("img");
const callback = (res) => {
//res 是觀察的元素?cái)?shù)組 info 每個(gè)被觀察的圖片信息
// 循環(huán)所有觀察元素
res.forEach((info) => {
// isIntersecting 目標(biāo)是否被觀察到,返回布爾值
if (info.isIntersecting) {
// img 就是當(dāng)前的圖片標(biāo)簽
const img = info.target;
img.src = img.getAttribute("data-src");
// 真實(shí)地址替換后 取消對它的觀察
obs.unobserve(img);
console.log("觸發(fā)");
}
});
};
const obs = new IntersectionObserver(callback);// 實(shí)例化 IntersectionObserver
// 遍歷imgs所有的圖片,然后給每個(gè)圖片添加觀察實(shí)例
imgs.forEach((img) => {
// observe : 被調(diào)用的IntersectionObserver實(shí)例。給每個(gè)圖片添加觀察實(shí)例
obs.observe(img);
});
/* IntersectionObserver(callback)
callback回調(diào)觸發(fā)兩次,看見了出發(fā),看不見也觸發(fā)
*/
</script>
</body>
</html>警告:IE 不兼容
到此這篇關(guān)于Vue 圖片懶加載 之 Vue-Lazyload的使用的文章就介紹到這了,更多相關(guān)Vue-Lazyload 使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue + el-form 實(shí)現(xiàn)的多層循環(huán)表單驗(yàn)證
這篇文章主要介紹了vue + el-form 實(shí)現(xiàn)的多層循環(huán)表單驗(yàn)證,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下。2020-11-11
關(guān)于Vue背景圖打包之后訪問路徑錯(cuò)誤問題的解決
本篇文章主要介紹了關(guān)于Vue背景圖打包之后訪問路徑錯(cuò)誤問題的解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
Vue中video標(biāo)簽如何實(shí)現(xiàn)不靜音自動(dòng)播放
最近在做大屏展示需要在一開始播放引導(dǎo)視頻,產(chǎn)生自動(dòng)播放需求,下面這篇文章主要給大家介紹了關(guān)于Vue中video標(biāo)簽如何實(shí)現(xiàn)不靜音自動(dòng)播放的相關(guān)資料,需要的朋友可以參考下2023-01-01
vue3中的對象時(shí)為proxy對象如何獲取值(兩種方式)
使用vue3.0時(shí),因?yàn)榈讓邮鞘褂胮roxy進(jìn)行代理的所以當(dāng)我們打印一些值得時(shí)候是proxy代理之后的是Proxy<BR>對象,Proxy對象里邊的[[Target]]才是真實(shí)的對象,那么如何獲取這個(gè)值呢,下面下面給大家介紹兩種方式,感興趣的朋友一起看看吧2023-01-01
使用vue-cli創(chuàng)建vue項(xiàng)目介紹
這篇文章介紹了使用vue-cli創(chuàng)建vue項(xiàng)目的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01

