JS-简单手写图片懒加载管理器

176 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

前言

图片懒加载是比较常见的优化需求, 在实际开发中, 基本上都是使用其他开发者事先封装好的相关程序.

恰逢有空, 就决定自己手写一个.

基本思路

图片懒加载的原理十分简单, 基本上就是先不要给图片设置 src, 然后监听浏览器的滚动, 当 滚动距离 + 可视区域 > 图片的相对文档高度 时, 再设置图片元素的 src.

效果

实现

img处理

为了方便使用, 可以给 img 标签添加一个特殊的标识, 用于获取需要懒加载的图片, 同时, 为了方便得到图片的 src, 也可以将 src 存储在 img 标签上.

在本文中, 将懒加载的标识与图片地址的存储合为一个 dataset 属性, 名为data-lazy-src:

<img data-lazy-src="https://tse1-mm.cn.bing.net/th/id/OIP-C.SFXnRiI3y-8vGyVwlpeh2gHaLR?pid=ImgDet&rs=1" />

JS 实现

定义管理器

本文中使用一个 JSON 作为管理器, 其中懒加载需要的属性和方法, 定义如下:

const lazyLoadImg = {
  // 懒加载标识
  lazyFlag: 'data-lazy-src',
  // 存储地址的 dataset 字段
  dataField: 'lazySrc',

  // 图片元素列表
  imgList: [],

  // 监听滚动防抖定时器句柄
  debounceTimer: 0,

  // 滚动处理函数 
  _handleScroll: () => {},

  /** 更改懒加载标识 */
  changeLazyFlag (flag, field) {},

  /** 挂载懒加载 */
  mount () {},

  /** 卸载懒加载 */
  unmount () {},

  /** 挂载滚动监听 */
  _mountScrollHander () {},

  /** 
   * 卸载滚动监听
   * @param { boolean } loadAllImg 是否加载剩余的图片
   *  */
  _unmoutScrollHander (loadAllImg = true) {},

  /** 滚动处理函数 */
  _createHandleScroll () {},

  /** 获取符合要求的 img 元素 */
  getLazyImgList () {}
}

对上面这些方法和属性, 主要可以分成两个个部分:

  1. 获取需要懒加载的 img 元素;
  2. 滚动的监听处理

详细看下面的实现.

获取 img 元素

获取元素由 lazyLoadImg.getLazyImgList 实现, 主要通过元素的懒加载标识 lazyFlag 进行获取, 然后创建对应的列表,存储在 lazyLoadImg.imgList 中:

  /** 获取符合要求的 img 元素 */
  getLazyImgList () {
    // 获取存储 src dataset属性
    const dataField = this.dataField
    // 通过懒加载标识 `lazyFlag` 进行获取元素
    this.imgList = [...document.querySelectorAll(`img[${this.lazyFlag}]`)].map(item => {
      // 为了方便区分执行懒加载, 使用时间作为 title 区分, 实际开发中不需要
      item.title = (new Date()).toLocaleString()
      // 返回记录 img 信息的 JSON
      return {
        // 图片元素
        element: item,
        // 图片相对与文档的高度
        top: item.offsetTop,
        // 图片的地址
        src: item.dataset[dataField]
      }
    })
  }

上面有两个标识:
一个是 dateField, 用于获取放在图片元素的 dataset 上的图片地址;
一个是 lazyFlag, 懒加载的标识;

滚动的监听处理

首先, 利用闭包创建一个滚动的处理函数 _handleScroll, 因为监听需要可以取消, 所以必须保证 addEventListenerremoveEventListener 是同一个函数, 才能正确进行取消.

但是使用 addEventListener 挂载后, this 会指向挂载的元素, 所以得用闭包记录下 lazyLoadImgthis, 方便调用 lazyLoadImg 的数据和函数.

同时, 由于滚动一般都是连续的, 所以加一个防抖处理, 节约资源:

  /** 生成滚动处理函数 */
  _createHandleScroll () {
    const _this = this
    this._handleScroll = function () {
      // 防抖处理
      if (_this.debounceTimer) {
        clearTimeout(_this.debounceTimer)
      }
      this.debounceTimer = setTimeout(() => {
        const { innerHeight, scrollY } = window
        // 计算当前可视区域 + 滚动过的高度
        const viewHeight = innerHeight + scrollY
        const dataField = _this.dataField
        console.log(1)
        // 遍历, 处理符合条件的图片进行展示, 不符合的继续留在 imgList 中
        _this.imgList = _this.imgList.reduce((newList, item) => {
          console.log('newList:',newList)
          if (item.top <= viewHeight) {
            // 符合条件的图片, 设置上正确的地址
            item.element.src = item.src
            item.element.title = (new Date()).toLocaleString()
            delete item.element.dataset[dataField]
          } else {
            newList.push(item)
          }
          return newList
        }, [])
        // 如果不存在懒加载的图片, 卸载滚动监听事件
        if (_this.imgList.length <= 0) {
          _this._unmoutScrollHander()
        }
      }, 200)
    }
  }

然后就是挂载监听滚动事件以及卸载监听滚动事件:

  /** 挂载滚动监听 */
  _mountScrollHander () {
    window.addEventListener('scroll', this._handleScroll)
  },

  /** 
   * 卸载滚动监听
   * @param { boolean } loadAllImg 是否加载剩余的图片
   *  */
  _unmoutScrollHander (loadAllImg = true) {
    window.removeEventListener('scroll', this._handleScroll)
    // 将未加载的图片,一次性加载完成
    if (this.imgList.length > 0 && loadAllImg) {
      this.imgList.forEach((item) => {
        item.element.src = item.src
        item.element.title = (new Date()).toLocaleString()
        delete item.element.dataset[dataField]
      })
    }
  },

注意: 卸载时,需要对剩下未加载的图片进行处理, 默认直接进行加载.

启动 lazyLoaImg

上面已经实现了基本的流程, 接下来就是实现一个启动的函数:

  /** 开始挂载懒加载 */
  mount () {
    // 获取元素
    this.getLazyImgList()
    // 创建滚动处理函数
    this._createHandleScroll()
    // 挂载监听事件
    this._mountScrollHander()
    // 检测当是否存在需要加载的图片
    this._handleScroll()
  },

接着使用就很简单了, 代码如下:

// 导入
import lazyLoadImg from  'xsxxx.js'

// 启动
lazyLoadImg.mount()