先看demo:图片压缩
项目源码:github
效果如下:
前端实现压缩功能,从图片大小和质量两方面着手:缩小图片和降低图片质量
因此我们设计一个 imageCompress 类,传入一个 option, 其参数有:
file: url 或 file
width: 输出图片的宽度
height: 输出图片的高度
mineType: 输出图片的格式,默认为image/png
quality: 输出图片的画质。值为0~1,默认为1
因为图片的加载是一个异步的过程,因此我们需要借助 promise, 以new ImageCompress(option).then(instance => {})
创建并在 then 中调用实例的方法。因为 file 可以是 url 也可以是 file 对象,因此在构建函数中需对这两种情况分别判断,并在结束时返回 promise
class imageCompress {
constructor(options) {
this._img = null; // 原始图片
this._compressedImg = null; // 压缩后的图片
this._canvas = null; // 创建的canvas
this._blob = null; // 创建的blob
// 默认设置
this.options = {
mimeType: 'image/png',
quality: 1,
};
// 合并默认参数与用户参数
extend(this.options, options, true);
// 文件为传入抛出错误
if (!options.file) {
throw new Error('图片未传入');
}
let that = this;
if (typeof options.file === 'string') {
let img = (this._img = new Image());
// 解决跨域报错问题
img.setAttribute('crossOrigin', 'anonymous');
img.src = options.file;
return new Promise((resolve, reject) => {
img.onload = function() {
resolve(that);
};
img.onerror = function(...args) {
reject(new Error('图片加载失败'));
};
});
} else if (typeof options.file === 'object' && options.file.name) {
// 判断文件的后缀是否为图片
if (!isAssetTypeAnImage(options.file.name)) {
throw new Error('该文件不是图片');
}
let image = (this._img = new Image());
return new Promise((resolve, reject) => {
if (window.URL) {
image.src = window.URL.createObjectURL(options.file);
} else {
const reader = new FileReader();
reader.onload = e => {
image.src = e.target.result;
};
reader.onabort = () => {
reject(
new Error(
'Aborted to load the image with FileReader.'
)
);
};
reader.onerror = () => {
reject(
new Error(
'Failed to load the image with FileReader.'
)
);
};
reader.readAsDataURL(options.file);
}
image.onload = function() {
resolve(that);
};
image.onerror = function(...args) {
reject(new Error('图片加载失败'));
};
});
}
}
}
复制代码
我们在构建函数中已经将传入的图片加载并赋值给了 this._img
, 接下来创建一个 canvas, 并将此图片按设置的大小画出来,便得到目标 canvas; 替换一个节点,先找出其父节点,并用一个新节点替换 oldNode.parentNode.replaceChild(newNode, oldNode);
// 获取canvas,可用于二次加工绘制
getCanvas() {
if (!this._canvas) this._imagetoCanvas();
return this._canvas;
}
// 私有方法,图片转canvas
_imagetoCanvas() {
let image = this._img;
var cvs = (this._canvas = document.createElement('canvas'));
var ctx = cvs.getContext('2d');
cvs.width = this.options.width || image.width;
// 高度默认等比例压缩
cvs.height = this.options.width
? (this.options.width * image.height) / image.width
: image.height;
ctx.drawImage(image, 0, 0, cvs.width, cvs.height);
}
// 替换文档canvas节点
replaceCanvasNode(oldNode) {
let newNode = this.getCanvas();
// 使新节点具有旧节点的id,类名,样式
newNode.style.cssText = oldNode.style.cssText;
newNode.id = oldNode.id;
newNode.className = oldNode.className;
// 用新节点替换旧节点
oldNode.parentNode.replaceChild(this.getCanvas(), oldNode);
}
前一步我们已经能够获取 canvas,将 canvas 调用 canvas.toDataURL(this.options.mimeType, this.options.quality)
即可获取 base64
getImageBase64() {
let canvas = this.getCanvas()
return canvas.toDataURL(this.options.mimeType, this.options.quality);
}
获取blob调用 canvas.toBlob(callback,mimeType,quality)
, 由于此过程也是异步,因此返回 promise
// 获取压缩后的文件,return promise.resolve(blob)
getCompressFile() {
if (!this._canvas) this._imagetoCanvas();
let that = this;
return new Promise((resolve, reject) => {
that._canvas.toBlob(
blob => {
that._blob = blob;
resolve(blob);
},
that.options.mimeType, // 图片类型
that.options.quality // 图片质量
);
});
}
复制代码
令 this._compressedImg
指向压缩后的图片,我们的目标是找到 image 的 src 属性,有两种方法 URL.createObjectURL(blob)
和 new FileReader().readAsDataURL(blob)
, 因此我们需调用第 4 步实现的方法 getCompressFile
获取 blob
// 获取压缩后的图片节点
getCompressImageNode() {
// 如果压缩后的图片已经创建,则不需要重复创建,返回即可
if (this._compressedImg && this._compressedImg.src)
return Promise.resolve(this._compressedImg);
let image = (this._compressedImg = new Image());
return this.getCompressFile().then(blob => {
if (window.URL) {
image.src = window.URL.createObjectURL(blob);
} else {
const reader = new FileReader();
reader.onload = e => {
image.src = e.target.result;
};
// 终止事件
reader.onabort = () => {
return Promise.reject(
new Error('Aborted to load the image with FileReader.')
);
};
reader.onerror = () => {
return Promise.reject(
new Error('Failed to load the image with FileReader.')
);
};
reader.readAsDataURL(blob);
}
return Promise.resolve(image);
});
}
// 替换页面图片
replaceImageNode(oldNode) {
this.getCompressImageNode().then(image => {
image.style.cssText = oldNode.style.cssText;
image.id = oldNode.id;
image.className = oldNode.className;
oldNode.parentNode.replaceChild(image, oldNode);
});
}
复制代码
当 this._compressedImg
被赋值且其 src 属性存在时,可以直接创建 a 标签下载;若没有创建压缩后的 img, 则调用上一步创建的 getCompressImageNode()
方法获取压缩后的 img, 再进行下载
// 下载压缩后的文件
downloadCompressFile(name = 'compress-file') {
if (this.blob && this._compressedImg) {
const dataURL = this._compressedImg.src;
const link = document.createElement('a');
link.download = name;
link.href = dataURL;
link.dispatchEvent(new MouseEvent('click'));
} else {
this.getCompressImageNode().then(image => {
const dataURL = image.src;
const link = document.createElement('a');
link.download = name;
link.href = dataURL;
link.dispatchEvent(new MouseEvent('click'));
});
}
}
复制代码
对上述7个功能进行测试,效果及代码如下:
new imageCompress({
file:
'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1477436123,3577936229&fm=26&gp=0.jpg',
width: 500,
quality: 1,
mimeType: 'image/jpeg',
})
.then(instance => {
// 获取canvas,可用于自行加工绘制
let canvas = instance.getCanvas();
let context = canvas.getContext('2d');
context.moveTo(100, 100);
context.lineTo(50, 50);
context.stroke();
// 替换文档中存在图片节点
instance.replaceImageNode(document.getElementById('img'));
// 替换文档中存在的canvas节点
instance.replaceCanvasNode(document.getElementById('canvas'));
// 获取压缩后生成的image节点
instance.getCompressImageNode().then(image => {
console.log(image)
});
// // 获取压缩后的blob文件,可用于上传
instance.getCompressFile().then(blob => {
});
// 获取图片base64
let base64 = instance.getImageBase64();
// 下载压缩后文件
// instance.downloadCompressFile();
})
.catch(err => {
console.log(err);
});
复制代码
以上是我能想到的图片压缩的7个功能,如果你有想到其他的需求,欢迎在评论区留言。如果文中有错漏,也欢迎指出!