前端图片压缩上传
图片压缩
背景
最近业务中遇到了用户抱怨说上传图片时间太长,感觉上是系统特别慢。
所以打算对图片上传进行优化,前端压缩后再上传文件。
方案
经过搜索后目前大家常用的图片压缩方案有这么几种
canvas 前端压缩 (移动端对 canvas 支持情况较好,能解决现有问题)
对图片尺寸压缩(打卡需要用户与背景的合照,不能对尺寸压缩会丢失信息)上传图片后端压缩(需要解决的问题是上传慢,这种方法不适合当前场景)
对比后我选择了第一种方案,就是 canvas 压缩图片的方案
实现
流程
有个前提条件 由于项目中是调用用户相机拍照得到的文件,所以我不需要 check 文件类型
检查文件大小是否需要压缩
使用 FileReader 对图片文件进行读取,并转化为 Base64 编码的 dataURL
用上一步得到的 Base64 编码的图片转化为一个 Image DOM 对象 (因为 canvas 的 drawImage 方法传入的参数只支持 CanvasImageSource)
调用 canvas 接口画出图像并转为压缩后的 dataURL
将压缩后的文件上传到服务器
file2DataURL 实现
// 图片文件转 Base64 编码
const file2DataURL = (file, callback) => {
const reader = new FileReader();
reader.onload = function () {
const dataURL = this.result;
callback(dataURL);
};
reader.readAsDataURL(file);
};
dataURL2Image 实现
// dataURL 转 img 对象
const dataURL2Image = (dataURL, callback) => {
const img = new Image();
img.onload = () => {
callback(img);
};
img.src = dataURL;
};
canvasCompress 实现
// canvas 压缩图片
const canvasCompress = (img, quality, type) => {
const canvas = document.createElement('canvas');
const { width, height } = img;
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, width, height);
const compressedDataURL = canvas.toDataURL(type, quality);
return compressedDataURL;
};
dataURL2Blob 实现
// dataURL 转 blob 流
const dataURL2Blob = (dataURL, type) => {
const text = window.atob(dataURL.split(",")[1]);
const buffer = new ArrayBuffer(text.length);
const ubuffer = new Uint8Array(buffer);
for (let i = 0; i < text.length; i++) {
ubuffer[i] = text.charCodeAt(i);
}
const blob = new window.Blob([buffer], { type });
return blob;
};
compress 实现
/**
* @param {File} source 图片源文件
* @param {Number} quality 压缩质量 - 默认不压缩 0 ~ 1
* @param {Number} maxSize 大小限制 - 默认 1 MB,(1M以上就要压缩)
*/
const compress = (source, callback, quality = 1, maxSize = 1) => {
// quality 参数错误不压缩直接返回
if(quality > 1) {
return callback(source);
}
// 单位转换
maxSize = maxSize * 1024 * 1024;
// 对 img 参数做简单的判断;
if(!source || !(source instanceof File)){
return callback(source);
}
// 图片小于限制则不压缩
if(source.size <= maxSize) {
return callback(source);
}
try {
const type = source.type;
file2DataURL(source, (dataURL) => {
dataURL2Image(dataURL, (img) => {
const compressedDataURL = canvasCompress(img, quality, type);
const compressedResult = dataURL2Blob(compressedDataURL, type);
compressedResult.fileName = source.name;
callback(compressedResult);
});
});
} catch (e) {
// process error
}
};
目前使用回调的方式调用最终看起来会有点乱,大家也可以优化改成 Promise + async await 的方式这样看起来结构会好很多。
参考文档
CanvasRenderingContext2D.drawImage()