最近做的一个微信活动中需要用到图片上传功能,具体就是用户通过手机拍照上传图片来参与活动。在测试的过程中我们发现上传的图片非常大。我们知道现在的手机拍出的图片都非常的大,一般是2~5M,甚至10多M的都有。这么大的图片上传上来,用户的上传体验非常不好,而且给服务器带宽增加了很大的压力,服务器的磁盘空间也会被渐渐撑爆,即使改成上传到oss,也不是很好的办法。于是前端同事就想到先用js压缩图片再上传。我就跟着偷偷学习一下。
js压缩图片
/**
* js压缩图片
* @param file 图片文件对象
* @param quality 压缩倍率 0~1
* @constructor
*/
function compressImage(file, quality) {
// 图片小于1M不压缩
if (file.size < Math.pow(1024, 2)) {
return file;
}
//默认0.5倍压缩
quality = quality || 0.5;
//保存文件名,后边用到
var name = file.name;
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function (e) {
var src = e.target.result;
var img = new Image();
img.src = src;
img.onload = function (e) {
var w = img.width;
var h = img.height;
//生成canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// 创建属性节点
var anw = document.createAttribute("width");
anw.nodeValue = w;
var anh = document.createAttribute("height");
anh.nodeValue = h;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
//铺底色 PNG转JPEG时透明区域会变黑色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, w, h);
ctx.drawImage(img, 0, 0, w, h);
// quality值越小,所绘制出的图像越模糊
var base64 = canvas.toDataURL('image/jpeg', quality); //图片格式jpeg或webp可以选0-1质量区间
// 返回base64转blob的值
console.log('\u539F\u56FE' + (src.length / 1024).toFixed(2) + 'kb', '\u65B0\u56FE' + (base64.length / 1024).toFixed(2) + 'kb');
//去掉url的头,并转换为byte
var bytes = window.atob(base64.split(',')[1]);
//处理异常,将ascii码小于0的转换为大于0
var ab = new ArrayBuffer(bytes.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
//通过Blob生成新图片文件对象
file = new Blob([ab], { type: 'image/jpeg' });
//这里需要重新设置文件名
file.name = name;
return file;
};
img.onerror = function (e) {
console.error(e)
};
};
reader.onerror = function (e) {
console.error(e)
};
};
主要用到的原生组件:FileReader、Canvas、Blob、FormData
逻辑步骤:
- FileReader.readAsDataURL将上传的图片文件转为Base64格式
- 将img绘制到canvas上,canvas.toDataURL压缩图片
- new Blob将压缩后的Base64转为Blob格式
- FormData.append将图片文件数据存入formdata
注意问题:
- PNG转JPEG时PNG格式的透明区域会变黑色,需要先手动铺底色
- toDataURL参数为PNG时不支持传图片质量,所以需要写image/jpeg或image/webp,具体可以参考toDataURL的api
- 需要先保存文件名,然后生成新的图片后设置文件名为原来的文件名。
- formData.append第三个参数filename是有浏览器兼容性问题的,如果不传,图片到后台的文件名会变成blob (filename=blob),后端校验文件名可能过不去。我们就遇到了这个问题。
ajax 上传文件
/**
* ajax上传文件
* @param file 文件对象
*/
function upload(file) {
var formData = new FormData();
formData.append("file", file, file.name);
$.ajax({
url: 'asianCup/uploadBarCode',
dataType: 'json',
type: "POST",
cache: false,
contentType: false, //必须
processData: false, //必须
data: formData,
async: true,
success: function (data) {
if (data.success) {
alert("上传成功");
} else {
//异常提示
alert(data.code + "-" + data.msg);
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.error(errorThrown);
if (XMLHttpRequest.responseText.indexOf("400") > -1) {
//接口或参数出错
alert("网络不给力,请稍后再试!");
} else {
//服务器重启
alert("网络不给力,请稍后再试!");
}
}
});
}
ajax上传文件需要构建一个formDate对象,将文件appendj进去,然后把formDate对象放到ajax的data参数即可,如果页面本身已经有表单了,也可以直接放到data,并且下面二个参数是必须的:
contentType: false,//必须false才会自动加上正确的Content-Type
processData: false,//必须false才会避开jQuery对 formdata 的默认处理,XMLHttpRequest会对 formdata 进行正确的处理