前言
圖片懶載入在一些圖片密集型的網站中運用比較多,通過圖片懶載入可以讓一些不可視的圖片不去載入,避免一次性載入過多的圖片導致請求阻塞(瀏覽器一般對同一域名下的併發請求的連線數有限制),這樣就可以提高網站的載入速度,提高使用者體驗。
效果預覽
如何做
第一步: 首先我們需要讓我們html中需要懶載入的img標籤的src
設定縮圖或者不設定src
,然後自定義一個屬性,值為真正的圖片或者原圖的地址(比如下面的data-src),並且定義一個類名,表示該圖片是需要懶載入的(比如下面例子的lazy-image),這有兩個作用:
1、為以後獲取需要懶載入圖片的img元素
2、可以給這個類名設定背景圖片,作為圖片未載入前的過度圖片,比如顯示為loading的圖片。
<img src="https://tb1.bdstatic.com/tb/cms/liveshow/ent_slid2.jpg" class="lazy-image"/>
// css部分
.lazy-image {
background: url(`../img/loading.gif`) no-repeat center;
}
複製程式碼
第二步:頁面載入完後,我們需要獲取所有需要懶載入的圖片的元素集合,判斷是否在可視區域,如果是在可視區域的話,設定元素的src屬性值為真正圖片的地址。
inViewShow() {
let imageElements = Array.prototype.slice.call(document.querySelectorAll(`.lazy-image`))
let len = imageElements.length
for(let i = 0; i < len; i++) {
let imageElement = imageElements[i]
const rect = imageElement.getBoundingClientRect() // 出現在視野的時候載入圖片
if(rect.top < document.documentElement.clientHeight) {
imageElement.src = imageElement.dataset.src // 移除掉已經顯示的
imageElements.splice(i, 1)
len--
i--
}
}
}複製程式碼
這裡判斷是否出現在可視區域內,是通過獲取元素的getBoundingClientRect
屬性的top
值和頁面的clientHeight
進行對比,如果top
值小於clientHeight
,則說明元素出現在可視區域了。BoundingClientRect
是獲取某個元素相對於視窗的位置集合,見下圖,注意bottom
和right
和我們平時的right
和bottom
不一樣。
第三步:當使用者滾動視窗的時候,遍歷所有需要懶載入的元素,通過每個元素的BoundingClientRect
屬性來判斷元素是否出現在可視區域內,判斷方法同第二步一樣。
document.addEventListener(`scroll`, inViewShow)
複製程式碼
這裡我們可以優化下,可以通過函式節流優化滾動事件的處理函式。
利用高階特性Intersection Observer來判斷元素是否可見
上面我們利用元素的BoundingClientRect
的top
屬性和body的clientHeight
來判斷元素是否可見,這種傳統方式獲取元素是否可見的一個缺點是我們還需要繫結scroll事件,scroll事件是伴隨著大量計算的,會造成資源浪費,雖然我們可以通過節流函式來提高效能,但還是會有效能浪費的問題,而Intersection
Observer
可以不用監聽scroll事件,做到元素一可見便呼叫回撥,在回撥裡面我們來判斷元素是否可見。
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
// 如果元素可見
if (entry.isIntersecting) {
let lazyImage = entry.target
lazyImage.src = lazyImage.dataset.src
lazyImage.classList.remove("lazy-image")
lazyImageObserver.unobserve(lazyImage)
// this.lazyImages.splice(index, 1)
}
})
})
this.lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
})
}複製程式碼
完整程式碼
class LazyImage {
constructor(selector) {
// 懶記載圖片列表,將偽陣列轉為陣列,以便可以使用陣列的api
this.imageElements = Array.prototype.slice.call(document.querySelectorAll(selector))
this.init()
}
inViewShow() {
let len = this.imageElements.length
for(let i = 0; i < len; i++) {
let imageElement = this.imageElements[i]
const rect = imageElement.getBoundingClientRect()
// 出現在視野的時候載入圖片
if(rect.top < document.documentElement.clientHeight) {
imageElement.src = imageElement.dataset.src
// 移除掉已經顯示的
this.imageElements.splice(i, 1)
len--
i--
if(this.imageElements.length === 0) {
// 如果全部都載入完 則去掉滾動事件監聽
document.removeEventListener(`scroll`, this._throttleFn)
}
}
}
}
throttle(fn, delay = 15, mustRun = 30) {
let t_start = null
let timer = null
let context = this
return function() {
let t_current = +(new Date())
let args = Array.prototype.slice.call(arguments)
clearTimeout(timer)
if(!t_start) {
t_start = t_current
}
if(t_current - t_start > mustRun) {
fn.apply(context, args)
t_start = t_current
} else {
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
}
init() {
// 通過IntersectionObserver api判斷圖片是否出現在可視區域內,不需要監聽Scroll來判斷
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
// 如果元素可見
if (entry.isIntersecting) {
let lazyImage = entry.target
lazyImage.src = lazyImage.dataset.src
lazyImage.classList.remove("lazy-image")
lazyImageObserver.unobserve(lazyImage)
// this.lazyImages.splice(index, 1)
}
})
})
this.lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
})
} else {
this.inViewShow()
this._throttleFn = this.throttle(this.inViewShow)
document.addEventListener(`scroll`, this._throttleFn.bind(this))
}
}
}
// 呼叫例子
new LazyImage(`.lazy-image`)
複製程式碼
git地址:github.com/VikiLee/Laz…