圖片懶載入

大雄沒了哆啦A夢發表於2019-03-04

前言 

圖片懶載入在一些圖片密集型的網站中運用比較多,通過圖片懶載入可以讓一些不可視的圖片不去載入,避免一次性載入過多的圖片導致請求阻塞(瀏覽器一般對同一域名下的併發請求的連線數有限制),這樣就可以提高網站的載入速度,提高使用者體驗。 

效果預覽 

圖片懶載入

如何做 

第一步: 首先我們需要讓我們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是獲取某個元素相對於視窗的位置集合,見下圖,注意bottomright和我們平時的rightbottom不一樣。 

圖片懶載入

第三步:當使用者滾動視窗的時候,遍歷所有需要懶載入的元素,通過每個元素的BoundingClientRect屬性來判斷元素是否出現在可視區域內,判斷方法同第二步一樣。 

document.addEventListener(`scroll`, inViewShow)
複製程式碼

這裡我們可以優化下,可以通過函式節流優化滾動事件的處理函式。

利用高階特性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.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…

相關文章