3

基于promise的图片资源一次性加载或者预加载

作者:NEXT卓

场景描述

不是每个网页端的用户都能用得起光纤,不是每张图片都是压缩得很小,有时候我们也想要看高清大图,但是受限于网速有时候场景是这样的:(很明显左边的第一张图片还没出来,其他的就出来了)
1513151723x-1404795467.png
图片资源预加载是一个很常见的需求,在网页开发中,

  • 譬如我们在开发一个基于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  // 是否返回异步对象
})

201712131601114pzti.png

应用实例

  • 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
    }
}

nextBoy
422 声望50 粉丝

床前明月光