图片懒加载实现方式

懒加载原理

当浏览器滚动到相应的位置时再渲染相应的图片。主要利用图片src属性的重新赋值来实现。

img标签中的src的路径设置为空或默认图片(空白图片、Loading图),缓存真正的图片路径,当浏览器滚动到相应的位置时,再给src属性重新赋值。

懒加载目的

优化前端压力,减少首屏请求数,延迟请求。对服务器压力有一定缓解作用。

但是若图片较大,则会带来较差的用户体验。

方案一:使用滚动事件scroll实现

img标签中的src的路径设置为同一张图片(空白图片),将真正的图片路径保存在data-src属性中,通过scrollTop方法获取浏览器窗口顶部与文档顶部之间的距离,通过clientHeight方法获取可视区高度。scroll触发时,执行事件载入data-src(对每个img标签DOM来求其offsetTop,如果距离顶部的高度 >=可视区高度+窗口距离文档顶部的距离 )。

.lazy-image { 
    background: url('../img/loading.gif') no-repeat center; 
} 
document.addEventListener('scroll', inViewShow)

function inViewShow() {     
    let imageElements = Array.prototype.slice.call(document.querySelectorAll('img.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--         
        }     
    } 
}

方案二:使用IntersectionObserver实现

IntersectionObserver可以异步监听 目标元素 与其 祖先元素文档视窗 (可视区viewport) 交叉状态的方法。文档详见Intersection Observer - Web API 接口参考 | MDN (mozilla.org)

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('img.lazy-image')
加载中...
加载中...