圖片懶加載

前言 

圖片懶加載在一些圖片密集型的網站中運用比較多,經過圖片懶加載可讓一些不可視的圖片不去加載,避免一次性加載過多的圖片致使請求阻塞(瀏覽器通常對同一域名下的併發請求的鏈接數有限制),這樣就能夠提升網站的加載速度,提升用戶體驗。 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是獲取某個元素相對於視窗的位置集合,見下圖,注意bottomright和咱們平時的rightbottom不同。 數組


第三步:當用戶滾動窗口的時候,遍歷全部須要懶加載的元素,經過每一個元素的BoundingClientRect屬性來判斷元素是否出如今可視區域內,判斷方法同第二步同樣。 瀏覽器

document.addEventListener('scroll', inViewShow)
複製代碼

這裏咱們能夠優化下,能夠經過函數節流優化滾動事件的處理函數。bash

利用高級特性Intersection Observer來判斷元素是否可見

上面咱們利用元素的BoundingClientRecttop屬性和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

相關文章
相關標籤/搜索