图片上传功能解析

前几年为了提高图片上传速度,引入了百度的webupload,效率大幅提升。后来有项目决定用react,但自带的upload功能并不能满足需求,而webupload也不支持react,就决定自己编写一个上传组件。一开始觉得这个功能流程很清晰,应该很容易就完成,但最后发现中间还是有很多坑点。

先梳理一下流程

1、点击图片选择按钮或file input弹出默认文件选择框

2、判断file size是否超出限定范围,决定提示错误或者继续往下走

3、利用FileReader,将选择图片转化成base64图片地址

4、将base64图片控制尺寸以后绘制到canvas上,并控制质量导出另一个base64图片地址

5、将最终的base64图片地址上传服务器保存,并返回可以访问的线上地址

但最终实现的时候,遇到几个问题

1、ios下横屏拍摄的图片,上传以后会变成竖向,最终解决方案是获取图片文件的orientation属性,并根据具体值来做旋转canvas操作

参数旋转角度
1
690°
8270°
3180°

2、问题1中获取到orientation为6或8的图片文件,动态创建以后,做不做appendChild这步操作,获取到的图片宽高会刚好相反

3、最后一步上传服务器,如果不支持ajax格式,就得转化成FormData

 

最终代码:

html结构

<input style="display:none;" type="file" id="fileInput" accept="image/*"/> 
<div id="picker" style="cursor:pointer;width:200px;height:200px;background:#ddd;"><img id="img-content" /></div>

1、获取orientation需要的几个方法

//第一步把base64转成arrybuffer对象,获取Orientation需要
		function base64ToArrayBuffer(base64) {
			base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
			var binary = atob(base64);
			var len = binary.length;
			var buffer = new ArrayBuffer(len);
			var view = new Uint8Array(buffer);
			for (var i = 0; i < len; i++) {
			  view[i] = binary.charCodeAt(i);
			}
			return buffer;
		  }	
		//第二步针对arraybuffer对象获取Orientation,获取Orientation需要
		  function getOrientation(arrayBuffer) {
			var dataView = new DataView(arrayBuffer);
			var length = dataView.byteLength;
			var orientation;
			var exifIDCode;
			var tiffOffset;
			var firstIFDOffset;
			var littleEndian;
			var endianness;
			var app1Start;
			var ifdStart;
			var offset;
			var i;
			// Only handle JPEG image (start by 0xFFD8)
			if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
			  offset = 2;
			  while (offset < length) {
				if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
				  app1Start = offset;
				  break;
				}
				offset++;
			  }
			}
			if (app1Start) {
			  exifIDCode = app1Start + 4;
			  tiffOffset = app1Start + 10;
			  if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
				endianness = dataView.getUint16(tiffOffset);
				littleEndian = endianness === 0x4949;

				if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
				  if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
					firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);

					if (firstIFDOffset >= 0x00000008) {
					  ifdStart = tiffOffset + firstIFDOffset;
					}
				  }
				}
			  }
			}
			if (ifdStart) {
			  length = dataView.getUint16(ifdStart, littleEndian);
			  for (i = 0; i < length; i++) {
				offset = ifdStart + i * 12 + 2;
				if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
				  // 8 is the offset of the current tag's value
				  offset += 8;
				  // Get the original orientation value
				  orientation = dataView.getUint16(offset, littleEndian);
				  // Override the orientation with its default value for Safari (#120)
					dataView.setUint16(offset, 1, littleEndian);  
				  break;
				}
			  }
			}
			return orientation;
		  }	
		  // ArrayBuffer对象 Unicode码转字符串,获取Orientation需要
		  function getStringFromCharCode(dataView, start, length) {
			var str = '';
			var i;
			for (i = start, length += start; i < length; i++) {
			  str += String.fromCharCode(dataView.getUint8(i));
			}
			return str;
		  }

 

2、主要流程代码

if(fileInput.size/1024 > 5120){
			//message.error('Image must smaller than 5MB!');
		}else{
			var reader = new FileReader(); 
			reader.readAsDataURL(fileInput); 		
			reader.onload=function(e){
				var localImg = document.getElementById('img-content');
				var _orientation;
			    localImg.src=e.target.result;
				localImg.onload = function(){
					var _width=this.width,_height=this.height,_ratio=_height/_width;
				    localImg.style.marginTop = ((_width * 0.6)-_height)/2 + 'px';
				}
				const image = new Image();
				image.src = e.target.result;
                //问题2出现的地方
				//document.body.appendChild(image)
				alert(image.width)
				alert(image.height)
				//获取到旋转值_orientation以后绘制canvas
			    image.onload = function(){	
					var _arrayBuffer = base64ToArrayBuffer(e.target.result),_bufferLength = _arrayBuffer.byteLength;
					_orientation = getOrientation(_arrayBuffer);
					var canvas = document.createElement("canvas"); //创建临时画布
					//document.body.appendChild(canvas)
					var _width=this.width,_height=this.height,_ratio=_height/_width;
					var _this = this;
					var drawOnCanvas=function (x,y){
						canvas.width = x;
						canvas.height = y;
						var imgWidth = x,imgHeight = y;
						var ctx = canvas.getContext("2d");
                        //问题1解决方案
						if(_orientation && _orientation != 1){
							switch(_orientation){
								case 6:     // 旋转90度
									canvas.width = imgHeight;    
                                    canvas.height = imgWidth;
									ctx.rotate(Math.PI / 2);
									ctx.drawImage(_this, 0, -imgHeight, imgWidth, imgHeight);
									break;
								case 3:     // 旋转180度
									ctx.rotate(Math.PI);    
									ctx.drawImage(_this, -imgWidth, -imgHeight, imgWidth, imgHeight);
									break;
								case 8:     // 旋转-90度 
									canvas.width = imgHeight;    
                                    canvas.height = imgWidth;
									ctx.rotate(3 * Math.PI / 2);    
									ctx.drawImage(_this, -imgWidth, 0, imgWidth, imgHeight);
									break;
							}
						}else{
							ctx.drawImage(_this, 0, 0, x, y);
						}
					}
					if(_width>800){
						drawOnCanvas(800,800*_ratio)	
					}else{
						drawOnCanvas(_width,_height)	
					}
					
					
					
					//设置导出图片质量
					var resultBase = canvas.toDataURL("image/jpeg", 0.9);
					$.ajax({
					 type:'post',
					 url:上传服务器地址,
					 dataType:'json',
					 data:{fcontent:resultBase},
					 success:function(){
					 }
					})

			    }		
			}
		}

3、如果服务器不支持ajax提交,那就将最终获取到的resultBase转化成FormData格式

function dataURItoBlob(dataURI) {
			// convert base64/URLEncoded data component to raw binary data held in a string
			var byteString;
			if (dataURI.split(',')[0].indexOf('base64') >= 0)
				byteString = atob(dataURI.split(',')[1]);
			else
				byteString = unescape(dataURI.split(',')[1]);

			// separate out the mime component
			var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

			// write the bytes of the string to a typed array
			var ia = new Uint8Array(byteString.length);
			for (var i = 0; i < byteString.length; i++) {
				ia[i] = byteString.charCodeAt(i);
			}
			return new Blob([ia], {type:mimeString});
		}

var formData = new FormData();
					formData.append("file", dataURItoBlob(resultBase),"1.jpg");
					fetch(tool.getApiPostUrl("上传地址"), {
							credentials: "include",
							method: 'POST',
							body: formData
						}).then(function (response) {response.text().then(function(responseText) {

						  
						  
					   }).catch(ex => {  
							// 发生错误  
							message.error('上传失败,请重新上传');
						})  
					});

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值