前几年为了提高图片上传速度,引入了百度的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 | 0° |
6 | 90° |
8 | 270° |
3 | 180° |
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('上传失败,请重新上传');
})
});