基于promise的图片资源一次性加载或者预加载
作者:NEXT卓
场景描述
不是每个网页端的用户都能用得起光纤,不是每张图片都是压缩得很小,有时候我们也想要看高清大图,但是受限于网速有时候场景是这样的:(很明显左边的第一张图片还没出来,其他的就出来了)
图片资源预加载是一个很常见的需求,在网页开发中,
- 譬如我们在开发一个基于canvas的游戏,涉及到图片资源的时候,为了游戏的体验,我们希望图片资源全部加载才开始游戏。
- 又譬如在一个多图展示的网页,由于图片过多或图片太大,我们希望图片加载完再一次性显示,而不是东一张西一张陆续显示,这时候也需要用图片一次性加载功能。
图片资源加载的原理
在浏览器向服务器发送请求的过程中,如果图片资源已经加载过一次了,则不会再从服务器加载同一个图片,
利用这个原理,我们的实现思路如下:
// 创建一个图片对象
let img = new Image()
img.src = '图片地址'
// 资源图片加载完毕
img.onload = function() {}
基于这个原理我封装了一个图片资源加载的函数,其结构如下
/**
* @description 图片资源加载函数
* 适用于canvas加载图片,返回的是一个promise异步对象,response 的值为一个资源对象
* @param {Object} config 参数设置,是一个对象
* config.sourceData - 资源对象 {key: value} 资源名:资源地址
* config.mode - 默认为false, 即使有失败也会返回, mode为true,开启严格模式,一个失败则全部不返回
* config.target - 要预加载的目标节点对象
* config.response - 默认false, 是否返回promise异步对象,true为返回
* @return {null || Promise}
*/
const loadImg = (config) => {
// 初始化设置参数
let sourceData = config.sourceData
let mode = config.mode || false
let target = config.target || []
let needRes = config.response || false
// 创建promise对象
let promise = new Promise((resolve, reject) => {
// 函数内部
// 完整代码在最底层
})
}
下面让我们先来看看这个函数的威力吧!
图片加载函数描述与使用展示
函数loadImg()用于图片加载(函数实现代码在最下面),接受的参数为一个设置对象config, 该对象有4个参数键值
- config.sourceData: 必选参数,资源对象 {key: value} 资源名:资源地址
- config.target:可选参数,要实现加载的目标节点集
- config.response:可选参数,默认false,不返回异步对象;设置为true,则返回,可通过then()进行下一步数据操作
-
config.mode: 可选参数,是否开启严格模式
- 值为false(默认值): 不开启严格模式,该模式下如果有部分图片加载失败,其余图片仍然显示
- 值为true: 开启严格模式,该模式下如果有部分图片加载失败,所有图片都不显示,如果需要返回结果,则返回结果是抛出一个错误
基本调用方式如下
// 图片资源对象, 对象的key值是自定义的
// 到时候如果有返回,返回的response对象的key值跟这个一样
let data = {
img1: 'http://plaechold.it/200x200',
img2: 'http://plaechold.it/200x200',
img3: 'http://plaechold.it/200x200'
}
// 需要预加载的节点集合
let images = document.querySelectorAll('img')
// 开启资源加载
loadImg({
sourceData: data, // 图片资源对象
target: images, // 预加载目标
mode: true, // 是否开启严格模式
response: false // 是否返回异步对象
})
应用实例
-
canvas中加载全部图片再绘制到canvas面板中
记住必须设置config.response 的值为 true 才能用then进行数据操作
let canvas = document.querySelector('canvas')
let context = canvas.getContext('2d')
// 图片资源对象
let data = {
bird: 'http://plaechold.it/200x200', // 一只鸟的图片
person: 'http://plaechold.it/200x200', // 一个人类的图片
tiger: 'http://plaechold.it/200x200' // 一只老虎的图片
}
// 资源加载
loadImg({
sourceData: data, // 图片资源对象
mode: true, // 是否开启严格模式
response: true // 返回异步对象,然后可以使用then获取结果进行下一步处理
})
.then(res => {
// res 也是一个对象{ bird: 图片对象, person: 图片对象, tiger: 图片对象 }
// 获取所有的图片对象
let images = Object.values(res)
images.forEach((value, index) => {
context.drawImage(value, index * 100, index * 100)
})
})
// 记住必须设置config.response 的值为 true 才能用then进行数据操作
html页加载完毕一次性展示全部图片实例
<!--在html中图片的src请别写上,也别写src="",因为空地址也浏览器也会去请求 -->
<img alt="1">
<img alt="2">
<img alt="3">
// 图片资源对象
let data = {
bird: 'http://plaechold.it/200x200', // 一只鸟的图片
person: 'http://plaechold.it/200x200', // 一个人类的图片
tiger: 'http://plaechold.it/200x200' // 一只老虎的图片
}
let images = document.querySelectorAll('img')
// 资源加载
loadImg({
sourceData: data, // 图片资源对象
target: images, // 要渲染的节点集
mode: true, // 是否开启严格模式
})
loadImg()如何在vue中优雅地实现图片加载
在vue中,由于vue是双向数据绑定,因此我们可以先这样:
假设我要加载的图片html代码如下
<img :src = "imgSrc.img1">
<img :src = "imgSrc.img2">
<img :src = "imgSrc.img3">
也就是说图片的地址是动态的,绑定着imgSrc
整个实现代码如下:
<template>
<div class="img-wrap">
<img :src = "imgSrc.img1">
<img :src = "imgSrc.img2">
<img :src = "imgSrc.img3">
</div>
</template>
<script >
export default {
data() {
return {
// 图片绑定的数据
imgSrc: {
img1: '',
img2: '',
img3: ''
},
// 要加载的图片地址
loadSrc: {
img1: 'http://placehold.it/100x100',
img2: 'http://placehold.it/200x200',
img3: 'http://placehold.it/300x300'
}
}
},
created() {
loadImg({
sourceData: this.loadSrc,
response: true // 开启返回response
})
.then(() => {
// 将加载好的图片地址赋值给imgSrc
this.imgSrc = Object.assign({}, this.loadSrc)
})
}
}
</script>
总结
基于这个函数的功能,我们可以做很多事情,除了上面的canvas绘图和展示图片外,还可以利用该
函数进行预加载,譬如当用户在浏览第一页的时候,我们可以先加载第二页的图片,具体的实现例子
这里就不多做展示了。简单的如下
let nextImages = document.querySelectorAll('.next-img') // 下一页的图片节点
// 用户只移动到按钮上面还没点击
button.hover = function() {
// 预加载图片资源
loadImg({
sourceData: data, // 图片资源对象
target: nextImages, // 要渲染的节点集
mode: true, // 是否开启严格模式
})
}
// 这样当用户click点击以后图片资源已经在hover的时候就加载了
完整的函数实现代码如下
/**
* @description 资源图片加载函数
* 适用于canvas加载图片,返回的是一个promise异步对象,response 的值为一个资源对象
* @param {Object} config 参数设置,是一个对象
* config.sourceData - 资源对象 {key: value} 资源名:资源地址
* config.mode - 默认为false, 即使有失败也会返回, mode为true,开启严格模式,一个失败则全部不返回
* config.target - 要预加载的目标节点对象
* config.response - 默认false, 是否返回promise异步对象,true为返回
* @return {null || Promise}
*/
const loadImg = (config) => {
// 初始化设置参数
let sourceData = config.sourceData
let mode = config.mode || false
let target = config.target || []
let needRes = config.response || false
// 创建promise对象
let promise = new Promise((resolve, reject) => {
// 资源加载进度
let loadNum = 0
// 资源加载的结果集
let response = {}
// 如果是非严格模式
if (mode === false) {
// 遍历加载每个资源
Object.keys(sourceData).forEach(key => {
let source = new Image()
// 失败或者成功都写入response
source.onload = source.onerror = () => {
response[key] = source
loadNum++
if (loadNum === Object.keys(sourceData).length) {
// 如果有目标对象
if (target) {
target.forEach((item, index) => {
item.src = Object.values(sourceData)[index]
})
}
// 成功
resolve(response)
}
}
// src的赋值放在onload和onerror事件后面,这样才能兼容IE
source.src = sourceData[key]
})
} else if (mode === true) { // 严格模式:一个失败直接结束且返回空对象
// 遍历加载每个资源
Object.keys(sourceData).forEach(key => {
let source = new Image()
// 成功则写入response
source.onload = () => {
response[key] = source
loadNum++
if (loadNum === Object.keys(sourceData).length) {
// 如果有目标对象
if (target) {
target.forEach((item, index) => {
item.src = Object.values(sourceData)[index]
})
}
resolve(response)
}
}
// 失败则返回空
source.onerror = () => {
// 失败
reject(new Error())
}
// src的赋值必须放在最后,兼容IE
source.src = sourceData[key]
})
}
})
// 结束
if (needRes) {
return promise
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。