图片懒加载记录

214 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

1.前言

图片懒加载在网上应该已经烂大街的东西。每次需要使用都直接百度谷歌一下问题就不大。 但是作为一个程序员还是在理解这玩意儿的基础上,有一个自己的知识数据库才行。所以决定整理一下代码。

图片懒加载的作用自不用说,提高页面性能,避免浏览器一次性访问服务器太多次导致的阻塞。在APP中还能提高长列表的稳定性,这里的稳定性指的是安卓手机在爱啪啪使用内存过多的时候,切换到后台之后会直接给爱啪啪杀死的情况。

2. 简单使用

思路:当image在未出现在可视窗的时候,不加载该image的网络图片,只使用本地图片进行展位(一般是公司logo加载图)

由上而知,我们需要:

  1. image真实数据和占位符

  2. image在页面中距离顶部的距离

    getBoundingClientRect()

  3. 当前页面滚动条距离容器顶部的距离

    window.addEventListener('scroll', () => {})
    

2.1 准备获取数据

const TEMP_IMG = [
 'http://www.baidu.com/xxx1.png',
 'http://www.baidu.com/xxx2.png',
 'http://www.baidu.com/xxx3.png',
 'http://www.baidu.com/xxx4.png',
 'http://www.baidu.com/xxx5.png',
]

import placeHold from './placeHold.png' 

2.2 div到容器顶部的距离

.lazy-img {
  background: url(placeHold)
}
<>
  {
    TEMP_IMG.map((item, index) => {
      return <img key={index} data-src={item}  class='lazy-img'/>
    })
  }
</> 
const getLazyImgNodeToArray = ({nodeClass}) => {
  let nodeArray = Array.from(document.querySelectorAll(nodeClass))
  return nodeArray
}

const showRealImg = () => {
  let nodes = getLazyImgNodeToArray({nodeClass: '.lazy-img'})
  let screenHeight = document.documentElement.clientHeight
  let len = nodes.length
    for (let i = 0; i < len; i++) {
        let nodeItem = nodes[i]
        const rect = nodeItem.getBoundingClientRect()        
        if (rect.top < screenHeight) {
            nodeItem.src = nodeItem.dataset.src           
            nodes.splice(i, 1)
            len--
            i--
        }
    }
}

2.3 监听当前位置做相应操作

不管是vue还是react还是RN还是小程序,都需要等待dom渲染之后

showRealImg()

window.addEventListener('scroll', () => {
  showRealImg()
}) 

3 加入简单的函数防抖(debounce)

不给无聊的人滑来滑去的。 监听scroll事件会有性能问题,节流函数可以极大的提升性能,也基本可以用。但是使用Intersection Observer当块到可视窗的时候进行就进行相应的操作。

showRealImg()

let timer = false
window.addEventListenner('scroll', () => {
  timer && clearTimeout(timer)
  timer = setTimeout(() => {
    showRealImg()
  }, timeout)
}) 

动态控制timeout,可以通过判断当前网速情况,网速越快timeout可以设置越小

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-img")
            lazyImageObserver.unobserve(lazyImage)              
        }          
    })        
})       
 
this.lazyImages.forEach(function(lazyImage) {          
    lazyImageObserver.observe(lazyImage)       
})

4. 长列表结合

image.png

5. 最简单的办法

<img loading='lazy' />

该方法是很简单,性能理论也会是最好的。而且省却了太多的时间了。 不过可惜的是现在还是处于测试阶段,很多浏览器是不支持的

6. 整理思路流程

  1. 初始化数据

  2. 加载真实图片逻辑

  3. 防抖节流函数接入

  4. 函数入口

  5. 兼容配置

  6. 导出

    function LazyImg(node) {
        this.nodes = Array.from(document.querySelectorAll(node))
    
        this.init = () => {
            if (typeof (window['IntersectionObserver'] !== 'undefined')) {
                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.forEach(function (lazyImage) {
                    lazyImageObserver.observe(lazyImage);
                })
            } else {
                _showRealImg()
                this._throttleFn = this.throttle(_showRealImg)
                document.addEventListener('scroll', this._throttleFn.bind(this))
            }
        }
    
        _showRealImg = () => {
            let len = this.nodes.length
    
            for (let i = 0; i < len; i++) {
                let imageElement = this.nodes[i]
                const rect = imageElement.getBoundingClientRect()
                if (rect.top < document.documentElement.clientHeight) {
                    imageElement.src = imageElement.dataset.src
                    this.nodes.splice(i, 1)
                    len--
                    i--
                    _clearListenner()
                }
            }
        }
    
        _clearListenner = () => {
            if (this.nodes.length === 0) {
                window.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)
                }
            }
        }
    }