圖片懶加載在一些圖片密集型的網站中運用比較多,經過圖片懶加載可讓一些不可視的圖片不去加載,避免一次性加載過多的圖片致使請求阻塞(瀏覽器通常對同一域名下的併發請求的鏈接數有限制),這樣就能夠提升網站的加載速度,提升用戶體驗。 css
第一步: 首先咱們須要讓咱們html中須要懶加載的img標籤的src
設置縮略圖或者不設置src
,而後自定義一個屬性,值爲真正的圖片或者原圖的地址(好比下面的data-src),而且定義一個類名,表示該圖片是須要懶加載的(好比下面例子的lazy-image),這有兩個做用: html
一、爲之後獲取須要懶加載圖片的img元素 git
二、能夠給這個類名設置背景圖片,做爲圖片未加載前的過分圖片,好比顯示爲loading的圖片。 github
<img data-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屬性值爲真正圖片的地址。api
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)
複製代碼
這裏咱們能夠優化下,能夠經過函數節流優化滾動事件的處理函數。bash
上面咱們利用元素的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.intersectionRatio > 0) {
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…app