其实里面包含很多知识的,刚好昨天复习了这块,还是系统梳理一下吧,希望帮到刚接触的人。
注意,这篇文章针对的是IE10+的浏览器,想要兼容PC端IE9以下请使用jquery form插件。
--------------------开始
1.文件上传
不考虑旧浏览器的话,现在前端单纯上传文件的主流方式是创建FormData装载文件,POST上传:
//html
<input type="file" accept="image/*" name="avatar" id="inputFile" multiple />
//js
var InputObject = document.getElementById('inputFile');
var formdata = new FormData();
formdata.append('avatar',InputObject.files[0])
accept特性控制可选择文件类型,显式声明multiple可选择多个文件。
注意,react中fetch方式上传时,headers不要写Content-Type。
2.图片预览
当然仅上传文件是不够的,我们还需要在界面预览已选择的图片,目前有两种常用做法:
FileReader读取文件数据:
//选择图片后,读取文件并转为dataURL,赋给img标签的src
function inputFileOnChange(){
let fileReader = new FileReader();
fileReader.onload=function(e){
let preViewImg = new Image();
preViewImg.src = e.target.result;
previewBox.appendChild(preViewImg);
}
fileReader.readAsDataURL(inputFileObject.files[0])
}
FileReader.readAsDataURL执行后的result是一个base64编码的ascii字符串,原理是将3个8位字节转为4个6位字节,故而base64编码后数据反而更大。想将之解码,需要先截除其开头的“data:*/*;base64,”部分,再将剩余部分解码。但是result可以直接赋予img.src,在客户端本地展示图片。
//创建一个createObjectURL对象,并被img.src引用
let urlData = URL.createObjectURL(inputFileObject.files[0])
img.src = urlData;
//清除
URL.revokeObjectURL(urlData)
以前在项目中用过这种方式,本地预览时比FileReader方式快很多。但是有两点需要注意:
一是每次调用URL.createObjectURL都会创建一个全新独立的ObjectURL对象,该对象是一个DOMString字符串。
二是创建的ObjectURL对象的生命周期是和当前文档绑定的,当不需要继续引用该对象时,切记要调用URL.revokeObjectURL()清除该对象,否则会一直占据内存。
3.图片处理
除了预览图片之外,我们往往需要在上传前让用户做一些简单编辑:确定选区、选择高宽比、裁剪大小,并压缩。
这个环节我们借用了canvas,主要是canvas.getContext("2d").drawImage()和canvas.toDataURL()方法。
CanvasRenderingContext2D.drawImage() : 我们会将图片中被选区边界确定的部分绘制到canvas上,当然绘制前我们会先调整canvas元素的高宽及比例以适应选框。
//Demo
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
...
//resizeCanvas仅是将canvas高宽设成1:1
function resizeCanvas(length){
canvas.width = length;
canvas.height = length;
}
function clipImage(url,callback){
let img = new Image(),that=this;
img.src = url;
img.onload=function(){
let minLength = Math.min(img.height,img.width);
resizeCanvas(minLength);
context.drawImage(img,
(img.width-minLength)/2,
(img.height-minLength)/2,
minLength,
minLength,
0,
0,
minLength,
minLength
);
callback.call(that,canvas.toDataURL('image/png'));
}
}
drawImage()中第一个元素是图片源,一般取img、canvas或svg元素。当我们将图片绘制到canvas上后,就可以借助canvas.toDataURL()对其进行压缩。
HTMLCanvasElement.toDataURL() 的作用是 返回一段包含图片信息的dataURL(base64编码)字符串。其第一个参数确定图片的格式,默认'image/png',也可定义为'image/jpeg'。第二个参数定义图片质量,取值范围0~1。
当我们说用canvas压缩图片的时候,就是指canvas.toDataURL()第二个参数取值(0,1),降低了图片精度,从而压缩了数据。
但是toDataURL方法有两个隐藏的问题:
1.该方法不提供高宽参数,只会将整个canvas截屏转dataURL,这就是为什么我们要自己设定canvas的高宽以适应选区。
2.不同平台对canvas大小有限制,且不一。如果canvas的高或宽超出平台限制,toDataURL()只会返回一张透明图片的数据,详见Maximum size of a <canvas> element
4上传到后台
一开始说了,我们可以直接通过formdata将图片文件POST给后台,前端不需要做任何额外处理。但如果我们要上传的是经过canvas压缩的图片,是一段base64编码的dataURL。这时候如何做呢?
一个需要注意的前提,将base64数据解码时,解码的是“data:*/*;base64,”之后的部分。
因此典型的流程是:
1.将base64的头部标志去除,必要时记下其mime类型信息。
2.将dataURL剩余数据转为buffer
3.后台将buffer以图片扩展名写入服务器硬盘
因而在B/S流程上有两种提交方式【即,base64解码交给前端还是后台】:
第一种方式是,直接将base64字符串传给后台,后台获取后将“data:*/*;base64,”截除,再转buffer以图片后缀写入disk。但是浏览器对POST的字符串大小是有限制的。前端需要做检测,若大小超过某个值,再压缩。
第二种方式是,前端先处理base64字符串,转为file或blob,再装载到FormData,POST给后台。
下面是具体处理步骤,假设我们有个canvas压缩后的dataURL,存在变量urlData中:
//一个典型的转换函数
function convertBase64UrlToBlob(urlData){
let byteString=window.atob(urlData.split(',')[1]);
var ab = new ArrayBuffer(byteString.length);
var u8a = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
u8a[i] = byteString.charCodeAt(i);
}
return new Blob(u8a, {type : 'image/png'});
}
上面的几个api通常在处理二进制文件的时候才用到:
window.atob()用来解码base64格式的ascii字符串。使之每个char恢复为8位字节。
ArrayBuffer 的实例表示一段通用的、固定长度的二进制数据。ArrayBuffer实例往往不直接使用,而是用来创建一个TypedArray。
TypedArray描述一个类数组的二进制数据buffer,是一系列特定格式的对象的统称,如Int8Array、Uint8Array等。好比一组烧杯,有100ml的、500ml的、1L的。
上面转换函数中,我们将dataURL的每个字符的数据拷贝到u8a这个类数组的bufferr容器上,就可以传给Blob构造函数,生成一个Blob对象实例了,当然也可以传给File构造函数生成一个File对象实例。Blob和File构造函数都可以传入一个ArrayBuffer、ArrayBufferView(也就是int8Array这类东西)、Blob或DOMString生成其实例。
上面的操作中,我们用canvas.toDataURL拿到base64数据,再转buffer,再转Blob或File才装载进FormData提交后台。
其实IE10+浏览器里,canvas还提供了一个canvas.toBlob(callback,mimeType,qualityArgument)方法,除了多了callback参数,用法同toDataURL,可以直接返回一个blob对象。但是在IE10中实现不完全,需要prefix。所以开发者大多数时候还是宁愿先转base64再转blob或file。
至此,前端上传图片整个流程,算是梳理清楚了。我已经尽量简明了。
另推荐一些资料: