如何封装一个图片预加载功能

267 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第31天,点击查看活动详情

封装一个比较好用,功能完善,代码健壮的功能其实不是一个简单的事情,今天用一个图片预加载的功能需求来演示如何封装。

功能需求

  • 加载的图片可以是一个数组或一个对象,格式如下
var images = ['xxx.png', 'yyy.jpg']
var images = {
    p1: 'xxx.png',
    p2: 'yyy.png'
}
  • 全部加载完成后需要给用户一个提示,即让用户传入一个回调函数
function loadImage(images, callback) {
    // 如果全部加载完成,则调用回调函数,并传入一个参数
    callback && callback(true)
}
  • 支持如果超过一定的时间,则判断加载超时
function loadImage(images, callback, timeout) {
    var timeoutId
    var isTimeout
    if (timeout) {
       timeoutId = setTimeout(onSetTimeout, timeout)
    }
    function onSetTimeout() {
       isTimeout = true
       // false 表示加载失败
       callback(false)
    }
}

代码实现

function loadImage(images, callback, timeout) {
  // 加载图片的计数器
  var count = 0
  // 全部图片加载成功的一个标志位
  var success = true
  // 超时timer的id
  var timeoutId = 0
  // 是否加载超时的标志位
  var isTimeout = false
  // 对图片数据或对象进行遍历
  for (var key in images) {
    // 过滤prototype上的属性
    if (!images.hasOwnProperty(key)) {
      continue
    }
    // 获得每个图片元素
    // 期望格式是个object: {src: xxxx}
    var item = images[key]
    if (typeof item === 'string') {
      item = images[key] = {
        src: item
      }
    }
    // 如果格式不满足要求,则丢弃,遍历下一条数据
    if (!item || !item.src) {
      continue
    }

    // 计数器+1
    count++

    // 设置图片id,保证每次调用id都不同
    item.id = '__img__' + key + getId()
    // 设置图片元素的img,它是一个img对象
    item.img = window[item.id] = new Image()

    doLoad(item)
  }

  // 如果images是一个空数组或空对象,直接执行回调
  if (!count) {
    callback(success)
  } else if (timeout) {
    // 如果用户传递了超时时间
    timeoutId = setTimeout(onTimeout, timeout)
  }

  /**
   * 加载图片
   * @param item
   */
  function doLoad(item) {
    item.status = 'loading'
    var img = item.img
    // 定义图片加载成功或者失败的回调函数
    img.onload = function () {
      success = success && true
      item.status = 'loaded'
      done()
    }
    img.onerror = function () {
      success = false
      item.status = 'error'
      done()
    }
    // 发起一个http的图片请求
    img.src = item.src

    /**
     * 每张图片加载完成的回调函数
     */
    function done() {
      // 清理事件
      img.onload = img.onerror = null
      try {
        delete window[item.id]
      } catch (e) {}
      
      // 每张图片加载完成,计数器减一,当所有图片加载完毕且没有超时的情况下,
      // 清除超时计时器,且执行回调函数
      if (!--count && !isTimeout) {
        clearTimeout(timeoutId)
        callback(success)
      }
    }
  }

  function onTimeout() {
    isTimeout = true
    callback(false)
  }
}

var __id = 0
function getId() {
  return __id++
}

总结

上面的代码非常清晰,可以总结出以下几点供自己以后借鉴:

  • 支持多张图片的预加载:支持传入一个数组和对象,利用count变量来判断是否全部加载完成;
  • 支持用户传入回调函数:通过回调函数用户可以在所有图片加载完成或者失败之后,做一些自定义的逻辑操作;
  • 支持自定义的超时功能:通过传入一个请求时长,利用setTimeout来对比是否超时;
  • 代码考虑周全:当图片加载成功或者失败后,都会清除事件等,防止内存泄漏;
  • 逻辑划分清晰:每一个单一功能都封装成一个函数调用;