JavaScript優(yōu)化圖片懶加載的性能技巧
背景
前端發(fā)展過(guò)程中有許多性能優(yōu)化的操作,比如防抖、節(jié)流和圖片懶加載等。在這里我們首先聊聊圖片懶加載操作。
在最近的618中,我們會(huì)經(jīng)常逛像淘寶和京東等購(gòu)物平臺(tái)。那你覺(jué)得在淘寶頁(yè)面中的圖片資源是打開(kāi)頁(yè)面時(shí)就一次性全部加載完了呢,還是在你滾輪滾動(dòng)到的區(qū)域才加載圖片呢。

一次性全部加載會(huì)導(dǎo)致加載時(shí)間長(zhǎng)、網(wǎng)絡(luò)資源消耗大、內(nèi)存占用率高和發(fā)出大量圖片請(qǐng)求給服務(wù)器帶來(lái)的巨大壓力。
所以采用的是只加載當(dāng)前可見(jiàn)區(qū)域的圖片,隨著用戶的滾動(dòng),當(dāng)其他圖片進(jìn)入可見(jiàn)區(qū)域時(shí),再進(jìn)行加載。這種方法就是圖片的懶加載。這種方式可以有效地提高頁(yè)面的響應(yīng)速度,特別是在圖片數(shù)量較多或網(wǎng)絡(luò)條件較差的情況下,可以避免頁(yè)面加載緩慢或卡頓的現(xiàn)象,提供更好的用戶體驗(yàn)。
如下圖所示,淘寶頁(yè)面剛打開(kāi)時(shí)并不是全部加載的。

大致思路
圖片懶加載布局邏輯:
- 通過(guò)將
img標(biāo)簽的src屬性值都設(shè)置為同一個(gè)圖片的url,這個(gè)圖片需要盡可能小。這樣即可以為后續(xù)要真正加載的圖片占位置,也可以讓頁(yè)面布局更快地呈現(xiàn)出來(lái),而不是長(zhǎng)時(shí)間等待圖片加載導(dǎo)致空白。 - 將每一個(gè)
img標(biāo)簽中真正的圖片url存放在一個(gè)數(shù)據(jù)屬性當(dāng)中。當(dāng)需要加載時(shí)將該數(shù)據(jù)屬性內(nèi)的內(nèi)容賦值給img標(biāo)簽的src屬性。
圖片懶加載交互邏輯:
- 首先通過(guò)獲取用戶可視窗口高度、滾輪到最頂端的距離和每個(gè)圖片到最頂端的距離。
- 通過(guò)監(jiān)聽(tīng)器監(jiān)聽(tīng)滾輪滾動(dòng)事件觸發(fā)懶加載函數(shù)。
- 但是一開(kāi)始就出現(xiàn)在可視窗口的圖片需要直接加載,所以需要先運(yùn)行一次懶加載函數(shù)。
- 在懶加載函數(shù)里通過(guò)判斷(圖片到最頂端的距離)和(用戶可視窗口高度+滾輪到最頂端的距離)的大小判斷是否需要加載。
編輯代碼
html部分
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/hsossms/20240612/v2_8ec7812750194dbd831babce8806c626@000000_oswg5522709oswg1792oswg1024_img_png?x-oss-process=image/resize,m_mfit,w_600,h_400,limit_0/crop,w_600,h_400,g_center/format,webp" />
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567641293753_img_png">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567640518658_img_png">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642423719_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642425030_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642425101_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567642425061_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190904/v2_1567591358070_img_jpg">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567641974410_img_000">
<img src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-src="https://img.36krcdn.com/20190905/v2_1567641974454_img_000">
在html部分中有一個(gè)重要的步驟。你會(huì)發(fā)現(xiàn)每一個(gè)img標(biāo)簽的src屬性值都是一樣的,我們可以復(fù)制鏈接到瀏覽器查看該圖片。
該圖片是中間的小白點(diǎn):

該圖片的屬性如下:

可以看出這個(gè)圖片是非常小的。在頁(yè)面打開(kāi)時(shí),加載的都是這個(gè)圖片,真正要加載的圖片url放置在data-src數(shù)據(jù)屬性里面。最后通過(guò)JavaScript部分實(shí)現(xiàn)將data-src數(shù)據(jù)屬性的內(nèi)容賦值給img標(biāo)簽的src屬性,然后發(fā)送HTTP請(qǐng)求加載真正的圖片。
css部分
img {
display: block;
margin-bottom: 50px;
width: 400px;
height: 400px;
}
body {
background-color: gray;
}
通過(guò)設(shè)置img 標(biāo)簽樣式,讓小圖片給真正要放的圖片占位置。

JavaScript部分
- 變量定義:其中
imgs是包含頁(yè)面中所有<img>元素的集合,可以通過(guò)索引來(lái)訪問(wèn)具體的圖像元素;num表示圖片數(shù)量;n記錄被加載的圖片數(shù)量。
const imgs = document.getElementsByTagName('img');
const num = imgs.length;
let n = 0
- 在全局一個(gè)設(shè)置
scroll事件監(jiān)聽(tīng)器,當(dāng)事件觸發(fā)后會(huì)調(diào)用lazyload慢加載函數(shù)。
window.addEventListener('scroll', lazyload)
- 定義一個(gè)
lazyload慢加載函數(shù):
function lazyload() {
//可視區(qū)域的高度
let screenHeight = document.documentElement.clientHeight;
//滾動(dòng)條距離最頂部的距離,
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
//判斷圖片是否存在在可視區(qū)域內(nèi)
for (let i = n; i < num; i++) {
if (imgs[i].offsetTop > scrollTop + screenHeight) {
break;
} else {
//主動(dòng)觸發(fā)下載
imgs[i].src = imgs[i].getAttribute('data-src');
//記錄已經(jīng)加載過(guò)的圖片數(shù)量
n = i + 1;
if (n === num) {
//全部加載完畢后移除滾動(dòng)事件
window.removeEventListener('scroll', lazyload);
}
}
}
}
首先獲取可視窗口的高度
screenHeight、滾動(dòng)條距離最頂部的距離scrollTop和具體圖片距離最頂端的距離imgs[i].offsetTop。通過(guò)for循環(huán)遍歷每個(gè)img元素。- 當(dāng)
imgs[i].offsetTop > scrollTop + screenHeight時(shí),也就是說(shuō)該圖片的區(qū)域并沒(méi)有被可視區(qū)域覆蓋過(guò),所以圖片不需要加載,也就沒(méi)有別的后續(xù)操作了。 - 如果
imgs[i].offsetTop <= scrollTop + screenHeight時(shí),說(shuō)明該圖片的區(qū)域被可視區(qū)域覆蓋了,圖片需要進(jìn)行加載。
- 當(dāng)
當(dāng)圖片需要加載時(shí),將真正的圖片url賦值給
src屬性。由imgs[i].src = imgs[i].getAttribute('data-src')實(shí)現(xiàn)。每次加載一張圖片
n的值就加一。當(dāng)n的值等于num的值時(shí),也就是所有圖片都加載完成后就不需要scroll事件監(jiān)聽(tīng)器了,所有通過(guò)window.removeEventListener('scroll', lazyload)清除監(jiān)聽(tīng)器。最后因?yàn)槭灼羶?nèi)的圖片需要直接加載,而不是通過(guò)
scroll事件監(jiān)聽(tīng)器實(shí)現(xiàn)加載。所有需要調(diào)用一次慢加載函數(shù)。
//方法一
document.addEventListener('DOMContentLoaded', lazyload)
//方法二
window.addEventListener('load', lazyload)
方法一通過(guò)
DOMContentLoaded事件監(jiān)聽(tīng)器觸發(fā)慢加載函數(shù)的速度比方法二通過(guò)load事件監(jiān)聽(tīng)器觸發(fā)慢加載函數(shù)的速度快。所以推薦方法一。
節(jié)流優(yōu)化
因?yàn)?code>scroll事件監(jiān)聽(tīng)器在頻繁的滑輪滾動(dòng)會(huì)頻繁觸發(fā)。如果直接在事件處理函數(shù)中執(zhí)行大量復(fù)雜的操作,可能會(huì)導(dǎo)致性能問(wèn)題。
所以通過(guò)使用節(jié)流限制事件觸發(fā)的頻率。
const imgs = document.getElementsByTagName('img');
const num = imgs.length;
//用變量記錄節(jié)流返回的函數(shù)
const throttleLazyLoad = throttle(lazyload, 200);
//滾動(dòng)事件觸發(fā)懶加載
window.addEventListener('scroll', throttleLazyLoad)
let n = 0
//首屏加載,DOMContentLoaded事件是DOM加載完成,不包括圖片(比load事件快)
document.addEventListener('DOMContentLoaded', lazyload)
//首屏加載,load事件是DOM加載完成,包括圖片(比DOMContentLoaded事件慢)
window.addEventListener('load', lazyload)
//懶加載函數(shù)
function lazyload(event) {
//可視區(qū)域的高度
let screenHeight = document.documentElement.clientHeight;
//滾動(dòng)條距離最頂部的距離,
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
//判斷圖片是否存在在可視區(qū)域內(nèi)
for (let i = n; i < num; i++) {
if (imgs[i].offsetTop > scrollTop + screenHeight) {
break;
} else {
//主動(dòng)觸發(fā)下載
imgs[i].src = imgs[i].getAttribute('data-src');
//記錄已經(jīng)加載過(guò)的圖片數(shù)量
n = i + 1;
if (n === num) {
//全部加載完畢后移除滾動(dòng)事件
window.removeEventListener('scroll', throttleLazyLoad);
}
}
}
}
//節(jié)流函數(shù)
function throttle(func, limit) {
let inThrottle;
return function () {
const context = this;
const args = arguments;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
定義了一個(gè)節(jié)流函數(shù),它接收要執(zhí)行的函數(shù) func 和時(shí)間間隔限制 limit。內(nèi)部通過(guò)一個(gè)變量 inThrottle 來(lái)標(biāo)記當(dāng)前是否處于節(jié)流狀態(tài)。當(dāng)執(zhí)行返回的函數(shù)時(shí),先判斷如果不在節(jié)流狀態(tài),就立即執(zhí)行目標(biāo)函數(shù),并將 inThrottle 設(shè)置為 true,同時(shí)使用 setTimeout 在指定時(shí)間間隔后將 inThrottle 恢復(fù)為 false,從而實(shí)現(xiàn)了在規(guī)定時(shí)間間隔內(nèi)只執(zhí)行一次函數(shù)的節(jié)流效果,避免了頻繁觸發(fā)導(dǎo)致的性能問(wèn)題。
再次優(yōu)化
在日常工作時(shí),如果讓你選擇手搓一個(gè)節(jié)流函數(shù)和直接使用工具庫(kù)里的函數(shù),你肯定也會(huì)和我一樣偷懶,選擇直接使用工具庫(kù)里的現(xiàn)成的函數(shù)。
首先在HTML中使用以下代碼引入 Lodash 庫(kù)。
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
然后可以刪除掉你手搓的節(jié)流函數(shù)了,使用 Lodash 庫(kù)里的節(jié)流函數(shù)。
const imgs = document.getElementsByTagName('img');
const num = imgs.length;
//用變量記錄節(jié)流返回的函數(shù)
const throttleLazyLoad = _.throttle(lazyload, 200);//調(diào)用Lodash庫(kù)里的節(jié)流函數(shù)
//滾動(dòng)事件觸發(fā)懶加載
window.addEventListener('scroll', throttleLazyLoad)
let n = 0
//首屏加載,DOMContentLoaded事件是DOM加載完成,不包括圖片(比load事件快)
document.addEventListener('DOMContentLoaded', lazyload)
//首屏加載,load事件是DOM加載完成,包括圖片(比DOMContentLoaded事件慢)
window.addEventListener('load', lazyload)
//懶加載函數(shù)
function lazyload(event) {
//可視區(qū)域的高度
let screenHeight = document.documentElement.clientHeight;
//滾動(dòng)條距離最頂部的距離,
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
//判斷圖片是否存在在可視區(qū)域內(nèi)
for (let i = n; i < num; i++) {
if (imgs[i].offsetTop > scrollTop + screenHeight) {
break;
} else {
//主動(dòng)觸發(fā)下載
imgs[i].src = imgs[i].getAttribute('data-src');
//記錄已經(jīng)加載過(guò)的圖片數(shù)量
n = i + 1;
if (n === num) {
//全部加載完畢后移除滾動(dòng)事件
window.removeEventListener('scroll', throttleLazyLoad);
}
}
}
}
呈現(xiàn)效果
當(dāng)所有圖片加載完后滾動(dòng)事件就不會(huì)觸發(fā)慢加載函數(shù)了,并且也有節(jié)流效果。

首屏的圖片立即加載,區(qū)域圖片在滾動(dòng)到再加載。

以上就是JavaScript優(yōu)化圖片懶加載的性能技巧的詳細(xì)內(nèi)容,更多關(guān)于JavaScript優(yōu)化圖片懶加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js自定義鼠標(biāo)右鍵的實(shí)現(xiàn)原理及源碼
這篇文章主要介紹了js自定義鼠標(biāo)右鍵的實(shí)現(xiàn)原理及源碼,需要的朋友可以參考下2014-06-06
詳解webpack 多頁(yè)面/入口支持&公共組件單獨(dú)打包
這篇文章主要介紹了詳解webpack 多頁(yè)面/入口支持&公共組件單獨(dú)打包,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
JavaScript實(shí)現(xiàn)簡(jiǎn)單獲取當(dāng)前網(wǎng)頁(yè)網(wǎng)址的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)簡(jiǎn)單獲取當(dāng)前網(wǎng)頁(yè)網(wǎng)址的方法,通過(guò)location對(duì)象的href方法來(lái)獲取網(wǎng)址,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-11-11
利用JS實(shí)現(xiàn)獲取當(dāng)前系統(tǒng)電量情況
在前端瀏覽器中我們可以通過(guò)使用JavaScript的navigator.getBattery()方法來(lái)獲取當(dāng)前系統(tǒng)的電池情況,本文將介紹如何使用這個(gè)API以及它在實(shí)際應(yīng)用中的使用,需要的可以參考下2023-12-12
利用JS自動(dòng)打開(kāi)頁(yè)面上鏈接的實(shí)現(xiàn)代碼
今天經(jīng)過(guò)測(cè)試,實(shí)現(xiàn)了利用JS來(lái)自動(dòng)打開(kāi)頁(yè)面上的鏈接的功能,其實(shí)比較簡(jiǎn)單,就是在頁(yè)面上把鏈接列表列出來(lái),然后通過(guò)JQuery的相關(guān)控制,在框架頁(yè)中把鏈接打開(kāi),具體能做什么用,大家自己想,哈哈。2011-09-09
js實(shí)現(xiàn)日期級(jí)聯(lián)效果
本篇文章主要是對(duì)js實(shí)現(xiàn)日期級(jí)聯(lián)效果的實(shí)例進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
頁(yè)面載入結(jié)束自動(dòng)調(diào)用js函數(shù)示例
當(dāng)頁(yè)面加載完成后自動(dòng)調(diào)用預(yù)先編好的js函數(shù),在某些特殊情況下還是比較實(shí)用的,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下2013-09-09

