观察者模式实现图片预加载,并开放事件监听接口

参考

需求

实现图片预加载功能。在每张图片加载成功和加载失败时,分别需要调用loadProgress和loadError函数;图片加载完毕后需要调用loadComplete函数。

可选:

  • 以上3个接口函数可以随时切换,并支持多次预加载。
  • 以上3个接口函数拓展为”事件“,可添加任意多个事件监听器。

技术栈:vue3。一开始之所以选vue3,是期望vue能比较方便地把预加载所得Image对象插入到DOM。我探究了许久,最后宣布期望落空。下文会探讨这一问题。

我们实现一个loader,并期望它可以这么用:

      let loader = new Loader()
      loader.addEvent(Loader.LOAD_PROGRESS, loadProgress1)
      loader.addEvent(Loader.LOAD_PROGRESS, loadProgress2)
      loader.addEvent(Loader.LOAD_COMPLETE, loadComplete)
      loader.addEvent(Loader.LOAD_ERROR, loadError)
      loader
        .load(['imgs/1.png', 'imgs/2.png'])
        .then(() => {
          // 第2次预加载
          loader.setEvent(Loader.LOAD_COMPLETE, loadComplete2)
          return loader.load(['imgs/4.png', 'imgs/3.png'])
        })
        .then(() => {
          // 第3次预加载
          return loader.load(['imgs/4.png', 'imgs/5.png'])
        })
        .then(() => {
          // 第4次预加载
          return loader.load(['imgs/1.png'])
        })

监听器函数大概长这样

      let loadComplete = resp => {
        Vue.nextTick(() => {
          console.log(resp.msg, `加载成功图片数:${resp.sucCount},失败数:${resp.failCount}`)
          this.imgData = resp.data
          this.drawCanvas()
        })
      }

所有监听器函数都有且只有1个参数:resp。这个参数包含所有相关的数据。resp的格式:

{URL: url, progress: 加载成功图片数占图片总数的比例}//单张图片加载成功
{errURL: url, msg: '加载失败!'}//单张图片加载失败
{
  data: [{
    img: Image实例1, url: 图片url1, succeed: 是否加载成功1
  },{
    img: Image实例2, url: 图片url2, succeed: 是否加载成功2
  }],
  msg: '加载完成!',
  sucCount: 加载成功图片数,
  failCount: 加载失败图片数
}//所有图片加载完成

我们写2个js文件,img_loader.js是Loader的实现,观察者模式实现图片预加载.js是Loader的使用。

Loader的实现

首先实现一个EventListener,就是标准的观察者模式。Loader则是使用一个EventListener对象来进行事件触发。我的观点和参考链接是不同的,参考链接在Loader里直接实现观察者模式,而我认为分离出来比较好。

load函数的整体框架:

imgs.forEach((url) => {
    let im = new Image()
    im.onload = () => {...}
    im.onerror = () => {...}
    im.src = url
})

因为是异步操作,所以我们需要用Promise封装一下,因此load函数应该返回一个Promise对象。

一开始我写得很丑很丑(被注释的那段代码),后来发现用Promise.all就可以写出画风正常的代码了。

  load(imgs) {
    let sucCount = 0
    return Promise.all(imgs.map(url => {
      return new Promise((resolve, reject) => {
        let img = new Image()
        img.onload = () => resolve({img, URL: url, progress: (++sucCount) / imgs.length})
        img.onerror = () => reject({img, errURL: url, msg: '加载失败!'})
        img.src = url
      }).then(res => {
        this.e.trigger(Loader.LOAD_PROGRESS, null, res)
        return {img: res.img, url, succeed: true}
      }, err => {
        this.e.trigger(Loader.LOAD_ERROR, null, err)
        return {img: err.img, url, succeed: false}
      })
    })).then(imgData => {
      this.e.trigger(Loader.LOAD_COMPLETE, null, {
        data: imgData,
        msg: '加载完成qwq!',
        sucCount,
        failCount: imgs.length - sucCount
      })
    })
  }

加载成功走到fulfilled分支,失败则走到rejected分支,如此保证Promise数组每个元素都是fulfilled的Promise对象。你看,相比于参考链接的代码,是不是简洁多了(逃)~

Image对象插入到DOM

我找了很久,vue似乎没有把HTMLElement和HTML绑定起来的办法。所以最后就直接操作DOM了……

网上各种劣质资料(别问,问就是csdn无

上一篇:webpack学习:配置css,图片,文件,react等


下一篇:Error:Node Sass version 6.0.0 is incompatible with ^4.0.0