需求
实现图片预加载功能。在每张图片加载成功和加载失败时,分别需要调用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无