背景
收集用户上传的图片是一件很常见的需求,然而随着现在设备像素越来越高,照片尺寸通常也越来越大。因此,在前端对用户上传的图片进行压缩变为一个刚需,尤其是在移动设备上,用户通常希望直接选择拍摄图片上传,由此有了本文。原理很简单,主要就是通过canvas读取图片重绘,所以以贴代码为主。
在线demo戳这儿
上代码
/**
* 图片压缩
* @param source {String|File} 传入待压缩图片来源,支持两种方式:
* 1直接传入一个网络图片url
* 2传入img file对象
* @param options {Object} 压缩配置,示例如下:
* {
* width: 100, 设置width固定值,高度随比例缩放
* height: 100, 设置height固定值,宽度随比例缩放,同时设置则固定宽高
* maxWidth: 100, 设置最大宽度不大于100,高度随比例缩放
* maxHeight: 100, 设置最大高度不大于100,宽度随比例缩放
* maxSide: 100, 设置最大边不大于100,另外边随比例缩放
* ratio: 0.5, 设置尺寸宽高同时缩放到指定比例,取值范围 0-1
* //上述参数如果同时设置,优先级由上到下生效
* quality: 0.5, 图片输出质量,取值范围0-1
* type: 'image/jpeg' 图片输出格式
* }
* @param cb {Function} 处理结果的回调,参数为(err, file),第一个参数为出错时的error对象,第二个为处理后的图片file对象
* @return void
*/
function compressImg(source, options, cb) {
var img = new Image();
var finalFileName = 'newImage';
img.onerror = function(err) {
cb(err);
};
img.onload = function() {
try {
options = options ? options: {};
// default width: origin width
var finalWidth = this.width || 100;
// default height: origin height
var finalHeight = this.height || 100;
// defalut quality: 0.6
var finalQuality = options.quality || 0.6;
// default type: 'image/jpeg'
var finalType = options.type || 'image/jpeg';
// calculate finalWidth & finalHeight
if (options.width && options.height) {
finalWidth = options.width;
finalHeight = options.height;
} else if (options.width && !options.height) {
finalHeight = parseInt(finalHeight * (options.width / finalWidth), 10);
finalWidth = options.width;
} else if (options.height && !options.width) {
finalWidth = parseInt(finalWidth * (options.height / finalHeight), 10);
finalHeight = options.height;
} else if (options.maxWidth) {
if (finalWidth > options.maxWidth) {
finalHeight = parseInt(finalHeight * (options.maxWidth / finalWidth), 10);
finalWidth = options.maxWidth;
}
} else if (options.maxHeight) {
if (finalHeight > options.maxHeight) {
finalWidth = parseInt(finalWidth * (options.maxHeight / finalHeight), 10);
finalHeight = options.maxHeight;
}
} else if (options.maxSide) {
if (finalHeight >= finalWidth && finalHeight > options.maxSide) {
finalWidth = parseInt(finalWidth * (options.maxSide / finalHeight), 10);
finalHeight = options.maxSide;
} else if (finalWidth > finalHeight && finalWidth > options.maxSide) {
finalHeight = parseInt(finalHeight * (options.maxSide / finalWidth), 10);
finalWidth = options.maxSide;
}
} else if (options.ratio) {
finalWidth = parseInt(finalWidth * options.ratio, 10);
finalHeight = parseInt(finalHeight * options.ratio, 10);
}
var canvas = document.createElement('canvas');
canvas.width = finalWidth;
canvas.height = finalHeight;
var context = canvas.getContext('2d');
context.clearRect(0, 0, finalWidth, finalHeight);
context.drawImage(this, 0, 0, finalWidth, finalHeight);
// 可以直接用canvas.toBlob,但是移动端兼容性一般
var newBlob = dataURItoBlob(canvas.toDataURL(finalType, finalQuality), finalType);
// 这一行ios设备可能会有兼容性问题(安卓OK),建议直接返回blob对象,不要调用new file生成新的file 对象
var newFile = new File([newBlob], finalFileName, {
type: finalType
});
console.log('After compress:' + newFile.size / 1024 + 'kb');
cb(null, newFile);
} catch(err) {
cb(err);
}
};
if (typeof source === 'string') {
img.setAttribute('crossOrigin', 'anonymous');
img.src = source;
finalFileName = source;
} else if (typeof source === 'object' && source.toString().indexOf('File') >= 0) {
console.log('Before compress:' + source.size / 1024 + 'kb');
var reader = new FileReader();
reader.readAsDataURL(source);
reader.onload = function(e) {
img.src = e.target && e.target.result;
if (source.name) {
finalFileName = source.name;
}
};
} else {
cb(new Error('Error image source context'));
}
};
function dataURItoBlob(dataURI, type) {
var img = dataURI.split(',')[1];
var decode = window.atob(img);
/* eslint-disable */
var ab = new ArrayBuffer(decode.length);
var ib = new Uint8Array(ab);
/* eslint-enable */
for (var i = 0; i < decode.length; i++) {
ib[i] = decode.charCodeAt(i);
}
return new Blob([ib], {
type: type
});
}
使用方法无比详细,就不多解释了。这里多说一点兼容性相关的问题。这个工具方法的callback返回的是一个新new的file对象,然而由于ios上存在的一些兼容性问题,方法最后返回的file对象在ios上大概率为空或者有各种问题,因此如果你是在移动端ios上使用此工具方法,建议修改一下,直接返回blob即可。另外,dataURLtoBlob方法其实有canvas的原生支持canvas.toBlob,只是兼容性较差,可以酌情考虑使用。
下面上几个使用的例子:
//Test upload
var input = document.querySelector('#test');
input.addEventListener('change', function(e) {
var file = e.target.files[0];
compressImg(file, {
quality: 0.6,
type: 'image/jpeg'
},
function(err, blob) {
if (err) {
console.log(err);
} else {
var url = URL.createObjectURL(blob);
console.log(url);
}
});
});
//Test web url
compressImg("https://img.yzcdn.cn/upload_files/2017/11/27/FuBfCauOKWsx6aWKCEqRkB3RKXu7.png", {
quality: 0.6,
type: 'image/jpeg'
},
function(err, blob) {
if (err) {
console.log(err);
} else {
var url = URL.createObjectURL(blob);
console.log(url);
}
});
The end