JavaScript優(yōu)化圖片懶加載的性能技巧
背景
前端發(fā)展過程中有許多性能優(yōu)化的操作,比如防抖、節(jié)流和圖片懶加載等。在這里我們首先聊聊圖片懶加載操作。
在最近的618中,我們會經(jīng)常逛像淘寶和京東等購物平臺。那你覺得在淘寶頁面中的圖片資源是打開頁面時(shí)就一次性全部加載完了呢,還是在你滾輪滾動到的區(qū)域才加載圖片呢。
一次性全部加載會導(dǎo)致加載時(shí)間長、網(wǎng)絡(luò)資源消耗大、內(nèi)存占用率高和發(fā)出大量圖片請求給服務(wù)器帶來的巨大壓力。
所以采用的是只加載當(dāng)前可見區(qū)域的圖片,隨著用戶的滾動,當(dāng)其他圖片進(jìn)入可見區(qū)域時(shí),再進(jìn)行加載。這種方法就是圖片的懶加載。這種方式可以有效地提高頁面的響應(yīng)速度,特別是在圖片數(shù)量較多或網(wǎng)絡(luò)條件較差的情況下,可以避免頁面加載緩慢或卡頓的現(xiàn)象,提供更好的用戶體驗(yàn)。
如下圖所示,淘寶頁面剛打開時(shí)并不是全部加載的。
大致思路
圖片懶加載布局邏輯:
- 通過將
img
標(biāo)簽的src
屬性值都設(shè)置為同一個(gè)圖片的url,這個(gè)圖片需要盡可能小。這樣即可以為后續(xù)要真正加載的圖片占位置,也可以讓頁面布局更快地呈現(xiàn)出來,而不是長時(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
屬性。
圖片懶加載交互邏輯:
- 首先通過獲取用戶可視窗口高度、滾輪到最頂端的距離和每個(gè)圖片到最頂端的距離。
- 通過監(jiān)聽器監(jiān)聽滾輪滾動事件觸發(fā)懶加載函數(shù)。
- 但是一開始就出現(xiàn)在可視窗口的圖片需要直接加載,所以需要先運(yùn)行一次懶加載函數(shù)。
- 在懶加載函數(shù)里通過判斷(圖片到最頂端的距離)和(用戶可視窗口高度+滾輪到最頂端的距離)的大小判斷是否需要加載。
編輯代碼
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è)重要的步驟。你會發(fā)現(xiàn)每一個(gè)img
標(biāo)簽的src
屬性值都是一樣的,我們可以復(fù)制鏈接到瀏覽器查看該圖片。
該圖片是中間的小白點(diǎn):
該圖片的屬性如下:
可以看出這個(gè)圖片是非常小的。在頁面打開時(shí),加載的都是這個(gè)圖片,真正要加載的圖片url放置在data-src
數(shù)據(jù)屬性里面。最后通過JavaScript部分實(shí)現(xiàn)將data-src
數(shù)據(jù)屬性的內(nèi)容賦值給img
標(biāo)簽的src
屬性,然后發(fā)送HTTP請求加載真正的圖片。
css部分
img { display: block; margin-bottom: 50px; width: 400px; height: 400px; } body { background-color: gray; }
通過設(shè)置img
標(biāo)簽樣式,讓小圖片給真正要放的圖片占位置。
JavaScript部分
- 變量定義:其中
imgs
是包含頁面中所有<img>
元素的集合,可以通過索引來訪問具體的圖像元素;num
表示圖片數(shù)量;n
記錄被加載的圖片數(shù)量。
const imgs = document.getElementsByTagName('img'); const num = imgs.length; let n = 0
- 在全局一個(gè)設(shè)置
scroll
事件監(jiān)聽器,當(dāng)事件觸發(fā)后會調(diào)用lazyload
慢加載函數(shù)。
window.addEventListener('scroll', lazyload)
- 定義一個(gè)
lazyload
慢加載函數(shù):
function lazyload() { //可視區(qū)域的高度 let screenHeight = document.documentElement.clientHeight; //滾動條距離最頂部的距離, 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 { //主動觸發(fā)下載 imgs[i].src = imgs[i].getAttribute('data-src'); //記錄已經(jīng)加載過的圖片數(shù)量 n = i + 1; if (n === num) { //全部加載完畢后移除滾動事件 window.removeEventListener('scroll', lazyload); } } } }
首先獲取可視窗口的高度
screenHeight
、滾動條距離最頂部的距離scrollTop
和具體圖片距離最頂端的距離imgs[i].offsetTop
。通過for循環(huán)遍歷每個(gè)img
元素。- 當(dāng)
imgs[i].offsetTop > scrollTop + screenHeight
時(shí),也就是說該圖片的區(qū)域并沒有被可視區(qū)域覆蓋過,所以圖片不需要加載,也就沒有別的后續(xù)操作了。 - 如果
imgs[i].offsetTop <= scrollTop + screenHeight
時(shí),說明該圖片的區(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)聽器了,所有通過window.removeEventListener('scroll', lazyload)
清除監(jiān)聽器。最后因?yàn)槭灼羶?nèi)的圖片需要直接加載,而不是通過
scroll
事件監(jiān)聽器實(shí)現(xiàn)加載。所有需要調(diào)用一次慢加載函數(shù)。
//方法一 document.addEventListener('DOMContentLoaded', lazyload) //方法二 window.addEventListener('load', lazyload)
方法一通過
DOMContentLoaded
事件監(jiān)聽器觸發(fā)慢加載函數(shù)的速度比方法二通過load
事件監(jiān)聽器觸發(fā)慢加載函數(shù)的速度快。所以推薦方法一。
節(jié)流優(yōu)化
因?yàn)?code>scroll事件監(jiān)聽器在頻繁的滑輪滾動會頻繁觸發(fā)。如果直接在事件處理函數(shù)中執(zhí)行大量復(fù)雜的操作,可能會導(dǎo)致性能問題。
所以通過使用節(jié)流限制事件觸發(fā)的頻率。
const imgs = document.getElementsByTagName('img'); const num = imgs.length; //用變量記錄節(jié)流返回的函數(shù) const throttleLazyLoad = throttle(lazyload, 200); //滾動事件觸發(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; //滾動條距離最頂部的距離, 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 { //主動觸發(fā)下載 imgs[i].src = imgs[i].getAttribute('data-src'); //記錄已經(jīng)加載過的圖片數(shù)量 n = i + 1; if (n === num) { //全部加載完畢后移除滾動事件 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)部通過一個(gè)變量 inThrottle
來標(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)致的性能問題。
再次優(yōu)化
在日常工作時(shí),如果讓你選擇手搓一個(gè)節(jié)流函數(shù)和直接使用工具庫里的函數(shù),你肯定也會和我一樣偷懶,選擇直接使用工具庫里的現(xiàn)成的函數(shù)。
首先在HTML中使用以下代碼引入 Lodash
庫。
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
然后可以刪除掉你手搓的節(jié)流函數(shù)了,使用 Lodash
庫里的節(jié)流函數(shù)。
const imgs = document.getElementsByTagName('img'); const num = imgs.length; //用變量記錄節(jié)流返回的函數(shù) const throttleLazyLoad = _.throttle(lazyload, 200);//調(diào)用Lodash庫里的節(jié)流函數(shù) //滾動事件觸發(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; //滾動條距離最頂部的距離, 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 { //主動觸發(fā)下載 imgs[i].src = imgs[i].getAttribute('data-src'); //記錄已經(jīng)加載過的圖片數(shù)量 n = i + 1; if (n === num) { //全部加載完畢后移除滾動事件 window.removeEventListener('scroll', throttleLazyLoad); } } } }
呈現(xiàn)效果
當(dāng)所有圖片加載完后滾動事件就不會觸發(fā)慢加載函數(shù)了,并且也有節(jié)流效果。
首屏的圖片立即加載,區(qū)域圖片在滾動到再加載。
以上就是JavaScript優(yōu)化圖片懶加載的性能技巧的詳細(xì)內(nèi)容,更多關(guān)于JavaScript優(yōu)化圖片懶加載的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript獲取當(dāng)前時(shí)間向前推三個(gè)月的方法示例
這篇文章主要介紹了JavaScript獲取當(dāng)前時(shí)間向前推三個(gè)月的方法,結(jié)合實(shí)例形式分析了javascript日期與時(shí)間運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2017-02-02Bootstrap table表格初始化表格數(shù)據(jù)的方法
這篇文章主要介紹了Bootstrap-table表格初始化表格數(shù)據(jù)的方法,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07js的for in循環(huán)和java里foreach循環(huán)的區(qū)別分析
這篇文章主要介紹了js的for in循環(huán)和java里foreach循環(huán)的區(qū)別,實(shí)例分析了js的for in循環(huán)使用技巧并說明了與Java中foreach循環(huán)的使用區(qū)別,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01elementui-樹形控件實(shí)現(xiàn)子節(jié)點(diǎn)右側(cè)添加圖標(biāo)和數(shù)據(jù)鼠標(biāo)放上去顯示文字效果
這篇文章主要介紹了elementui-樹形控件實(shí)現(xiàn)子節(jié)點(diǎn)右側(cè)添加圖標(biāo)和數(shù)據(jù)鼠標(biāo)放上去顯示文字效果,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-01-01微信小程序上傳圖片并等比列壓縮到指定大小的實(shí)例代碼
這篇文章主要介紹了微信小程序 上傳圖片并等比列壓縮到指定大小,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10使用webpack搭建pixi.js開發(fā)環(huán)境
這篇文章主要介紹了使用webpack搭建pixi.js開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02