JavaScript實(shí)現(xiàn)圖片懶加載的三種常用方法總結(jié)
1.前言
1.1 什么是使用圖片懶加載
懶加載是一種對(duì)網(wǎng)頁(yè)性能優(yōu)化的方式,比如當(dāng)訪問一個(gè)網(wǎng)頁(yè)的時(shí)候,優(yōu)先顯示可視區(qū)域的圖片而不是一次加載全部的圖片,當(dāng)需要顯示時(shí),再發(fā)送請(qǐng)求加載圖片。
1.2 為什么使用圖片懶加載
- 避免首次加載時(shí)消耗大量時(shí)間,降低頁(yè)面渲染速度,造成卡頓現(xiàn)象。
- 按需加載,避免無(wú)效圖片的加載,減輕服務(wù)器壓力,節(jié)約網(wǎng)絡(luò)資源。
若不使用圖片懶加載,頁(yè)面啟動(dòng)時(shí),會(huì)加載全部的圖片資源:
1.3 圖片懶加載的實(shí)現(xiàn)原理
1 基本原理: 監(jiān)聽圖片是否位于頁(yè)面的可視區(qū)域內(nèi),若在則加載圖片,不在則不加載圖片
2 實(shí)現(xiàn)方案: 自定義屬性-將圖片真實(shí)地址 url 存儲(chǔ)在自定義屬性中,當(dāng)監(jiān)聽到圖片進(jìn)入可視區(qū)域時(shí),將自定義屬性值賦值給 img 的 src 屬性
2.實(shí)現(xiàn)方法
2.1 利用元素的 getBoundingClientRect 方法實(shí)現(xiàn)
(1)屬性介紹:
利用.getBoundingClientRect
實(shí)時(shí)獲取物體的動(dòng)態(tài)位置
(2)實(shí)現(xiàn)方法:
步驟 1:監(jiān)聽頁(yè)面滾動(dòng)事件,lazyLoad
為頁(yè)面滾動(dòng)時(shí)的處理函數(shù),在本節(jié)為處理圖片懶加載。
window.addEventListener('scroll', lazyLoad)
步驟 2:判斷圖片是否處于可視區(qū)域內(nèi)
(1)若距離頂部top
小于頁(yè)面的整體高度window.innerHeight
(2)若距離左側(cè)left
小于頁(yè)面的整體寬度window.innerWidth
(3)同時(shí)圖片的底部bottom
與圖片的右側(cè)right
距頁(yè)面頂部、左側(cè)的距離均大于0
則說(shuō)明該圖在屏幕的可視區(qū)域內(nèi)。
為了提高復(fù)用性,我們可以將它封裝成一個(gè)自定義函數(shù)isVisible
,將每張圖片作為參數(shù)傳入該函數(shù),并返回true
或false
// 可視區(qū)域判斷函數(shù) function isVisible(img) { // 判斷是否在可視區(qū)域,并返回true或false const imgRect = img.getBoundingClientRect() // getBoundingClientRect 獲取圖片的動(dòng)態(tài)信息 return imgRect.bottom > 0 && imgRect.top < window.innerHeight && imgRect.right > 0 && imgRect.left < window.innerWidth }
步驟 3:定義圖片懶加載時(shí)的處理事件,監(jiān)聽所有的img
,判斷該img
是否處于可視范圍內(nèi)
querySelectorAll
獲取的元素為偽數(shù)組 需要轉(zhuǎn)為真數(shù)組,否則無(wú)法使用數(shù)組的某些方法
// 獲取所有的img元素,并利用擴(kuò)展運(yùn)算符轉(zhuǎn)為真數(shù)組 const images = [...document.querySelectorAll('img')]
步驟 4:對(duì)每張圖片進(jìn)行監(jiān)聽,利用自定義函數(shù)isVisible
判斷是否在可視區(qū)域內(nèi)
(1)若處于可視區(qū)域:將自定義的data-src
值,賦值給真正的src
屬性值,其中 data-src
存儲(chǔ)圖片的URL
地址,并刪除該元素防止重復(fù)加載
(2)若不處于可視區(qū)域:return 不做處理
// 利用循環(huán)判斷每張圖片是否屬于可視區(qū)域 function lazyLoad(){ for (let i = 0; i < images.length; i++) { // isVisible是否該圖片位于可視區(qū)域 返回true 或false if (isVisible(images[i])) { // 將元素的自定義屬性 data-src 賦值給元素的 src 屬性 // 等價(jià)于:img.setAttribute('src', img.getAttribute('data-src')) images[i].src = images[i].dataset.src // 防止重復(fù)被遍歷 加載完之后 刪除元素不再加載 images.splice(i, 1) i-- } } } lazyLoad()
(3)整體代碼:
// html 標(biāo)簽結(jié)構(gòu) <img data-src="./public/image/VCG211430870249.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211430987515.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211431054751.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211435102490.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211438229829.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211438109615.jpg" src="./public/image/默認(rèn).jpg" alt="">
// 1 獲取全部圖片的DOM節(jié)點(diǎn) // 注意:querySelectorAll 值為偽數(shù)組利用擴(kuò)展運(yùn)算符轉(zhuǎn)為真數(shù)組 const images = [...document.querySelectorAll('img')] // 2 監(jiān)聽頁(yè)面滾動(dòng)事件 window.addEventListener('scroll', lazyLoad) // 3 定義頁(yè)面滾動(dòng)的處理函數(shù) function lazyLoad(){ for (let i = 0; i < images.length; i++) { // isVisible是否該圖片位于可視區(qū)域 返回true 或false if (isVisible(images[i])) { // 將元素的自定義屬性 data-src 賦值給元素的 src 屬性 // dataset.src 此為元素的自定義屬性 data-src images[i].src = images[i].dataset.src // 等價(jià)于:img.setAttribute('src', img.getAttribute('data-src')) // 防止重復(fù)被遍歷 加載完之后 刪除元素不再加載 images.splice(i, 1) i-- } } } lazyLoad() // 4 可視區(qū)域判斷函數(shù) function isVisible(img) { // 判斷是否在可視區(qū)域 const imgRect = img.getBoundingClientRect() // getBoundingClientRect 獲取圖片的動(dòng)態(tài)信息 return imgRect.bottom > 0 && imgRect.top < window.innerHeight && imgRect.right > 0 && imgRect.left < window.innerWidth }
2.2 利用整體距離實(shí)現(xiàn)
(1)屬性介紹:
clientHeight : 網(wǎng)頁(yè)可見區(qū)域高
- A 表示可見區(qū)域的高度,包含
padding
不包含border
和margin
- B 語(yǔ)法:
element.clientHeight
- C 備注:
body.clientHeight
=window.innerHeight
innertHeight : window 整體高度
- A 表示
window
的內(nèi)部高度,包括縱向滾動(dòng)條 - B 語(yǔ)法:
window.innertHeight
offsetTop : 距離父級(jí)元素頂部的高度
- A 表示當(dāng)前元素相對(duì)于其
offsetParent
元素的頂部?jī)?nèi)邊距的距離 - B 語(yǔ)法:
element.offsetTop
scrollTop : 網(wǎng)頁(yè)被卷去的距離
- A 表示在有滾動(dòng)條時(shí),滾動(dòng)條向下滾動(dòng)的距離也就是元素頂部被遮住部分的高度
- B 語(yǔ)法:
element.scrollTop
(2)實(shí)現(xiàn)方法:
可以用image.offsetTop <= document.documentElement.clientHeight + document.documentElement.scrollTop
判斷圖片是否可以在可視區(qū)域內(nèi)。
- 圖片元素位置的頂部距離:
offsetTop
- 滾動(dòng)距離的最下端:
scrollTop+clientHeight
// html 標(biāo)簽結(jié)構(gòu) <img data-src="./public/image/VCG211430870249.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211430987515.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211431054751.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211435102490.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211438229829.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211438109615.jpg" src="./public/image/默認(rèn).jpg" alt="">
// 1 獲取全部圖片的DOM節(jié)點(diǎn) // 注意:querySelectorAll 值為偽數(shù)組利用擴(kuò)展運(yùn)算符轉(zhuǎn)為真數(shù)組 const images = [...document.querySelectorAll('img')] // 2 監(jiān)聽頁(yè)面滾動(dòng)事件 window.addEventListener('scroll', lazyLoad) // 3 定義頁(yè)面滾動(dòng)的處理函數(shù) function lazyload(e){ // 3.1 獲取屏幕的可視高度 const clientHeight = document.documentElement.clientHeight // 3.2 獲取屏幕的滾動(dòng)距離 const scrollTop = document.documentElement.scrollTop for (let i = 0; i < images.length; i++) { if (images[i].offsetTop < clientHeight + scrollTop) { images[i].setAttribute('src', images[i].getAttribute('data-src')) } } }
2.3 利用Intersection Observer實(shí)現(xiàn)
Intersection Observer
是一個(gè)比較新的api,他允許你追蹤目標(biāo)元素與其祖先元素或視窗的交叉狀態(tài),用他來(lái)檢測(cè)圖片是否進(jìn)入視口非常方便,不用再像之前綁定事件、計(jì)算距離等。
(1)屬性介紹:
- 利用
Intersection Observer
實(shí)例上的observe
和unobserve
方法,注冊(cè)或取消監(jiān)聽事件。 - 利用
isIntersecting
方法,判斷該圖片是否處于圖片與屏幕可視區(qū)域的交叉范圍內(nèi)。 - 注意:
Intersection Observer
實(shí)例會(huì)監(jiān)聽交叉狀態(tài),即出現(xiàn)和消失(觸發(fā)兩次),出現(xiàn)交叉狀態(tài)后會(huì)去調(diào)用new
的時(shí)候傳入的callback
回調(diào)函數(shù)
(2)實(shí)現(xiàn)方法:
步驟 1: 監(jiān)聽頁(yè)面滾動(dòng)事件,lazyLoad
為頁(yè)面滾動(dòng)時(shí)的處理函數(shù),在本節(jié)為處理圖片懶加載。
window.addEventListener('scroll', lazyLoad)
步驟 2: 創(chuàng)建圖片與可視區(qū)域交叉實(shí)例
callback
:
- 此為傳入的回調(diào)函數(shù),用于當(dāng)處于交叉狀態(tài)改變時(shí)進(jìn)行的處理函數(shù)
- 該函數(shù)會(huì)被觸發(fā)2次:圖片進(jìn)入視野時(shí)+圖片離開視野時(shí)
const observer = new IntersectionObserver(callback)
步驟 3: 利用observer
實(shí)例上的.observe(img)
方法,給每張圖片綁定觀察事件
// 給每一個(gè)圖片綁定觀察方法 imagess.forEach(img => { // 圖片進(jìn)入視野+離開視野時(shí)會(huì)觸發(fā)callback回調(diào)函數(shù) observer.observe(img) })
步驟 4: 定義圖片的懶加載事件
imgArr
:
- 可以獲得包含所有圖片的
isIntersecting
屬性的集合,該屬性可判斷是否在交叉區(qū)域內(nèi) target
為該圖片的標(biāo)簽元素
// callback 接收的參數(shù)為帶有監(jiān)聽所有圖片交叉屬性的集合 const callback = (imgArr) => { console.log('視圖交叉時(shí)觸發(fā),離開交叉時(shí)也觸發(fā)', imgArr) imgArr.forEach(e => { // 判斷是否在視野區(qū)域 if (e.isIntersecting) { e.target.src = e.target.dataset.src // 取消監(jiān)聽,避免重復(fù)加載同一張圖片 observer.unobserve(e.target) } }) }
(3)整體代碼:
// html 標(biāo)簽結(jié)構(gòu) <img data-src="./public/image/VCG211430870249.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211430987515.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211431054751.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211435102490.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211438229829.jpg" src="./public/image/默認(rèn).jpg" alt=""> <img data-src="./public/image/VCG211438109615.jpg" src="./public/image/默認(rèn).jpg" alt="">
// intersectionObserver 交叉觀察 : 目標(biāo)元素和可視窗口會(huì)產(chǎn)生交叉區(qū)域 const imagess = [...document.querySelectorAll('img')] // 2.1 創(chuàng)建視覺交叉的觀察實(shí)例 const observer = new IntersectionObserver(callback) // 2.2 給每一個(gè)圖片綁定觀察方法 imagess.forEach(img => { // 2.3 圖片進(jìn)入視野+離開視野時(shí)觸發(fā) - 回調(diào) observer.observe(img) }) // callback 接收的參數(shù)為帶有監(jiān)聽所有圖片交叉屬性的集合 const callback = (imgArr) => { console.log('視圖交叉時(shí)觸發(fā),離開交叉時(shí)也觸發(fā)', imgArr) // imgArr為 imgArr.forEach(e => { // 判斷是否在視野區(qū)域 if (e.isIntersecting) { e.target.src = e.target.dataset.src // 取消觀察追蹤,避免重復(fù)加載同一張圖片 observer.unobserve(e.target) } }) }
以上就是JavaScript實(shí)現(xiàn)圖片懶加載的三種常用方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript圖片懶加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解JavaScript創(chuàng)建數(shù)組的三種方式
這篇文章主要介紹了JavaScript創(chuàng)建數(shù)組的三種方式:直接聲明,?以對(duì)象方式創(chuàng)建數(shù)組和使用?Array.from()?方法創(chuàng)建,并通過代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-06-06javascript完美拖拽的實(shí)現(xiàn)方法
這篇文章介紹了javascript完美拖拽的實(shí)現(xiàn)方法,有需要的朋友可以參考一下2013-09-09使用fabric實(shí)現(xiàn)恢復(fù)和撤銷功能的實(shí)例詳解
在圖形編輯器中,撤銷和恢復(fù)是一個(gè)非常常見的功能了,但是搜了下,網(wǎng)上好像也沒有太多相關(guān)的文章 可能是因?yàn)閏anvas相關(guān)的資料確實(shí)太少了吧,所以本文給大家介紹了如何基于 fabric 實(shí)現(xiàn)恢復(fù)、撤銷功能,需要的朋友可以參考下2024-06-06Webpack打包時(shí)將文件內(nèi)聯(lián)方法實(shí)現(xiàn)
本文主要介紹了Webpack打包時(shí)將文件內(nèi)聯(lián)方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Javascript ES6中數(shù)據(jù)類型Symbol的使用詳解
Symbol類型是es6新增的一個(gè)數(shù)據(jù)類型,Symbol值通過Symbol函數(shù)生成Symbol類型是保證每個(gè)屬性的名字都是獨(dú)一無(wú)二的,對(duì)于一個(gè)對(duì)象由對(duì)個(gè)模塊構(gòu)成的情況非常有用,本文主要介紹了Javascript ES6中數(shù)據(jù)類型Symbol使用的相關(guān)資料,需要的朋友可以參考下。2017-05-05Mint-UI時(shí)間組件起始時(shí)間問題及時(shí)間插件使用
這篇文章主要介紹了Mint-UI時(shí)間組件起始時(shí)間問題的解決方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-08-08JS實(shí)現(xiàn)點(diǎn)擊文字對(duì)應(yīng)DIV層不停閃動(dòng)效果的方法
這篇文章主要介紹了JS實(shí)現(xiàn)點(diǎn)擊文字對(duì)應(yīng)DIV層不停閃動(dòng)效果的方法,實(shí)例分析了javascript操作div層的效果,非常實(shí)用,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03