当我们在前端h5上传图片的时候

其实里面包含很多知识的,刚好昨天复习了这块,还是系统梳理一下吧,希望帮到刚接触的人。

注意,这篇文章针对的是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,在客户端本地展示图片。

URL.createObjectURL

//创建一个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对象实例。BlobFile构造函数都可以传入一个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。

至此,前端上传图片整个流程,算是梳理清楚了。我已经尽量简明了。

另推荐一些资料:

HTML5 Canvas处理头像上传

jquery图片剪辑插件cropper.js

猜你喜欢

转载自blog.csdn.net/weixin_36094484/article/details/81429479