NextBoy / skill

web实战中的一些小套路
59 stars 0 forks source link

基于promise的图片资源加载 #1

Open NextBoy opened 6 years ago

NextBoy commented 6 years ago

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

场景描述

不是每个网页端的用户都能用得起光纤,不是每张图片都是压缩得很小,有时候我们也想要看高清大图,但是受限于网速有时候场景是这样的:(很明显左边的第一张图片还没出来,其他的就出来了)

图片资源预加载是一个很常见的需求,在网页开发中,

图片资源加载的原理

在浏览器向服务器发送请求的过程中,如果图片资源已经加载过一次了,则不会再从服务器加载同一个图片, 利用这个原理,我们的实现思路如下:

 // 创建一个图片对象
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个参数键值

基本调用方式如下

// 图片资源对象, 对象的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绘图和展示图片外,还可以利用该 函数进行预加载,譬如当用户在浏览第一页的时候,我们可以先加载第二页的图片,具体的实现例子 这里就不多做展示了。简单的如下

 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({})
                }
                // src的赋值必须放在最后,兼容IE
                source.src = sourceData[key]
            })
        }
    })
    // 结束
    if (needRes) {
        return promise
    }
}