/**
* @file 图片下载类
*/
import EventQueues from '@~lisfan/event-queues'
// 下载成功过的图片地址列表池
const loadedImageList = []
// base64格式匹配正则表达式
const BASE64_REG = /data:(.*);base64,/
// 私有方法
const _actions = {
/**
* 判断请求的图片地址与已请求过的地址是否是一致的
*
* @since 1.2.0
*
* @param {ImageLoader} self - 实例自身
* @param {string} imageSrc - 图片地址
*
* @returns {boolean}
*/
isSameResource(self, imageSrc) {
return imageSrc === self.$currentSrc
},
/**
* 获取图片扩展名
* 兼容如下几种图片格式
* - base64格式
* - 纯固定扩展结尾
* - 兼容类似又拍云的图片处理APi,如`path/to/source.jpg!both/100x100`这样的格式
*
* @since 1.2.0
*
* @param {string} imageSrc - 图片地址
*
* @returns {string}
*/
getExtension(imageSrc) {
const matched = imageSrc.match(BASE64_REG)
let EXT_REG
// 如果本身是base64
if (matched) {
EXT_REG = /image\/(.*)/
imageSrc = matched[1]
} else {
EXT_REG = /.*\.([a-zA-Z\d]+).*/
}
return imageSrc.replace(EXT_REG, '$1').toLocaleLowerCase()
},
/**
* 根据图片的后缀获取图片的mime类型
*
* @since 1.2.0
*
* @param {string} ext - 后缀名
*
* @returns {string} - 返回mime类型
*/
getMimeType(ext) {
const MIME_TYPE = {
jpg: 'image/jpeg',
jepg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp',
}
return MIME_TYPE[ext]
},
/**
* 简易ajax请求封装
*
* @since 1.2.0
*
* @async
*
* @param {string} url - 请求链接
* @param {string} [method='get'] - 请求方法
* @param {string} [type='json'] - 响应结果类型
*
* @returns {Promise}
*/
ajax(url, method = 'get', type = 'json') {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.responseType = type
xhr.addEventListener('load', () => {
if (xhr.readyState !== 4 | xhr.status !== 200) {
return reject(xhr)
}
resolve(xhr)
})
xhr.addEventListener('error', (err) => {
reject(err)
})
xhr.send()
})
},
/**
* blob转dataURL
*
* @since 1.2.0
*
* @async
*
* @param {Blob} blob - blob数据
*
* @returns {Promise}
*/
blobToDataURL(blob) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.addEventListener('load', (event) => {
resolve(event.target.result)
})
fileReader.addEventListener('error', (err) => {
reject(err)
})
fileReader.readAsDataURL(blob)
})
},
/**
* dataURL转blob
*
* @since 1.2.0
*
* @param {string} dataURL - dataURL数据
*
* @returns {Blob}
*/
dataURLToBlob(dataURL) {
const arr = dataURL.split(',')
const blobStr = atob(arr[1])
let bLen = blobStr.length
const u8arr = new Uint8Array(bLen)
while (bLen--) {
u8arr[bLen] = blobStr.charCodeAt(bLen)
}
const mime = arr[0].match(/:(.*?);/)[1]
return new Blob([u8arr], { type: mime })
},
/**
* canvas 转 dataURL
*
* @since 1.2.0
*
* @param {ImageLoader} self - 实例自身
* @param {HTMLImageElement} image - image实例
* @param {string} [format] - 输出的图片格式,默认保存原图片后缀格式
*
* @returns {string}
*/
canvasToDataURL(self, image, format) {
// 如果图片本身是base64
// 如果存在的是$image
// 否则进行转换
let canvas = document.createElement('CANVAS')
canvas.width = self.$naturalWidth
canvas.height = self.$naturalHeight
const context = canvas.getContext('2d')
context.drawImage(image, 0, 0)
const mimeType = format ? _actions.getMimeType(format) : _actions.getMimeType(self.$ext)
return canvas.toDataURL(mimeType)
}
}
/**
* ImaqgeLoader继承自EventQueues类
*
* @external EventQueues
*
* @see {@link http://lisfan.github.io/event-queues|EventQueues}
*/
/**
* @description
* [注] 继承了EventQueues类,附加的实例方法和属性请至{@link http://lisfan.github.io/event-queues|EventQueues API}文档查看
*
* @classdesc 图片下载类
*
* @class
* @extends EventQueues
*/
class ImageLoader extends EventQueues {
/**
* 默认配置选项
*
* @since 1.0.0
*
* @static
* @readonly
* @memberOf ImageLoader
*
* @type {object}
* @property {boolean} debug=false - 打印器调试模式是否开启
* @property {string} name='ImageLoader' - 打印器名称标记
*/
static options = {
name: 'ImageLoader',
debug: false,
}
/**
* 更新默认配置选项
*
* @since 1.0.0
*
* @see ImageLoader.options
*
* @param {object} options - 其他配置选项见{@link ImageLoader.options}
*
* @returns {ImageLoader}
*/
static config(options) {
// 以内置配置为优先
ImageLoader.options = {
...ImageLoader.options,
...options
}
return this
}
/**
* 构造函数
*
* @see ImageLoader.options
*
* @param {object} options - 其他配置选项见{@link ImageLoader.options}
*/
constructor(options) {
super({
...ImageLoader.options,
...options
})
}
/**
* load方法执行时绑定的image对象
*
* @since 1.0.0
*
* @readonly
*
* @type {Image}
*/
$image = undefined
/**
* fetch方法执行时绑定的blob对象
*
* @since 1.0.0
*
* @readonly
*
* @type {Blob}
*/
$blob = undefined
/**
* 存取实例的当前图片地址
*
* @since 1.2.2
*
* @private
*/
_currentSrc = undefined
/**
* 获取image实例的当前图片地址
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {string}
*/
get $currentSrc() {
return this._currentSrc
}
/**
* 获取image实例对应图片是否下载过
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {boolean}
*/
get $complete() {
return this.$image && this.$image.complete
}
/**
* 存取实例的对应图片的下载是否成功状态值
*
* @since 1.2.2
*
* @private
*/
_status = undefined
/**
* 获取image实例对应图片的下载是否成功状态值
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.2.2
*
* @getter
* @readonly
*
* @type {string}
*/
get $status() {
return this._status
}
/**
* 存取实例的对应图片是否已下载过状态
*
* @since 1.2.2
*
* @private
*/
_loaded = undefined
/**
* 获取image实例对应图片是否已下载过
*
* @since 1.2.2
*
* @getter
* @readonly
*
* @type {boolean}
*/
get $loaded() {
return this._loaded
}
/**
* 获取image实例的设置宽度
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {number}
*/
get $width() {
return this.$image && this.$image.width
}
/**
* 获取image实例对应图片的真实宽度
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {number}
*/
get $naturalWidth() {
return this.$image && this.$image.naturalWidth
}
/**
* 获取image实例的设置高度
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {number}
*/
get $height() {
return this.$image && this.$image.height
}
/**
* 获取image实例对应图片的真实高度
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {number}
*/
get $naturalHeight() {
return this.$image && this.$image.naturalHeight
}
/**
* 获取当前文件扩展名
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {string}
*/
get $ext() {
return this.$image && _actions.getExtension(this.$currentSrc)
}
/**
* 获取当前文件的mime类型
* 仅在调用{@link ImageLoader#fetch}方法时有效
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {string}
*/
get $mime() {
return this.$blob && this.$blob.type
}
/**
* 获取当前文件的大小,单位:字节
* 仅在调用{@link ImageLoader#fetch}方法后,该值有效
*
* @since 1.0.0
*
* @getter
* @readonly
*
* @type {number}
*/
get $size() {
return this.$blob && this.$blob.size
}
/**
* 载入图片
*
* @since 1.0.0
*
* @async
*
* @param {string} [imageSrc=''] - 图片地址
* @param {number} [width] - 图片显示的宽
* @param {number} [height] - 图片显示的高
*
* @returns {Promise}
*/
load(imageSrc = '', width, height) {
return new Promise((resolve, reject) => {
if (!_actions.isSameResource(this, imageSrc)) {
this.$blob = null
}
this._currentSrc = imageSrc
this.$image = new Image(width, height)
// // 启用跨域请求
// this.$image.crossOrigin = '*'
this.$image.addEventListener('load', () => {
this._logger.log('image load successed!')
this._status = 'success'
if (loadedImageList.indexOf(this.$currentSrc) >= 0) {
this._loaded = true
} else {
this._loaded = false
loadedImageList.push(this.$currentSrc)
}
this.emit('load').then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
})
})
this.$image.addEventListener('error', () => {
this._logger.log('image load failed!')
this._status = 'fail'
this.emit('error').then((result) => {
reject(result)
}).catch((err) => {
reject(err)
})
})
this.$image.src = imageSrc
})
}
/**
* 以ajax方式获取图片资源,该方式获取的资源可以统计资源的容量大小
* 此时,可以取实例上的{@link ImageLoader#$mime}和{@link ImageLoader#$size}两个实例属性
* [注] 若图片地址是dataURL格式,则直接返回dataURL,且{@link ImageLoader#$size}对应的是dataURL的容量大小(并不是原图片的容量大小)
*
* @since 1.0.0
*
* @async
*
* @param {string} [imageSrc=''] - 图片地址
*
* @returns {Promise}
*/
fetch(imageSrc = '') {
// 如果已经是base64格式
return new Promise((resolve, reject) => {
if (!_actions.isSameResource(this, imageSrc)) {
this.$image = null
}
this._currentSrc = imageSrc
const matched = imageSrc.match(BASE64_REG)
// 如果本身是base64
if (matched) {
this.$blob = _actions.dataURLToBlob(imageSrc)
return this.load(imageSrc).then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
})
}
return _actions.ajax(imageSrc, 'get', 'blob').then((result) => {
this.$blob = result.response
this.load(imageSrc).then((loadResult) => {
resolve(loadResult)
}).catch((err) => {
reject(err)
})
// if (loadedImageList.indexOf(this.$currentSrc) >= 0) {
// this._loaded = true
// } else {
// this._loaded = false
// loadedImageList.push(this.$currentSrc)
// }
// this.emit('load').then((result) => {
// resolve(result)
// }).catch((err) => {
// reject(err)
// })
}).catch((err) => {
reject(err)
// this.emit('error').then((result) => {
// reject(result)
// }).catch((err) => {
// reject(err)
// })
})
})
}
/**
* 输出base64格式
* [注] 请确保在是在调用{@link ImageLoader#load}或{@link ImageLoader#fetch}实例方法后调用该属性
*
* @since 1.0.0
*
* @async
*
* @param {string} [format] - 输出的图片格式,默认保存原图片后缀格式
*
* @returns {Promise}
*/
base64(format) {
return new Promise((resolve, reject) => {
// 如果ImageLoader#$image和ImageLoader#$dataURL都不存在,则抛出错误
if (!this.$blob && !this.$image) {
reject('image resource does not load! please use once (ImageLoader#load) or (ImageLoader#fetch) method.')
}
// 假如优先存在$image则优先处理
// if (this.$image) {
// 如果图片本身是base64
const matched = this.$currentSrc.match(BASE64_REG)
// 如果本身是base64
if (matched) {
return resolve(this.$currentSrc)
}
return resolve(_actions.canvasToDataURL(this, this.$image, format))
// }
// // 如果只存在$blob
// _actions.blobToDataURL(this.$blob).then((dataURL) => {
// // 加载图片,转换url
// // 若format与当前后缀格式不匹配,则进行格式转换
// const mime = _actions.getMimeType(format)
//
// // 如果相等
// if (!mime || this.$mime === mime) {
// return resolve(dataURL)
// }
//
// // 转化为base64格式
// const image = new Image()
//
// image.src = dataURL
//
// image.addEventListener('load', () => {
// return resolve(_actions.canvasToDataURL(this, image, format))
// })
// })
})
}
}
export default ImageLoader