移动端web头像上传实现截取和照片方向修复
实战所需js包:
jQuery、Jcrop、EXIF
本次实战功能是在 app 中的 我的客户 的客户信息页面中实现移动端web的头像上传,本次没有实现图像拖拽、缩放的触摸事件功能(Jcrop在这方面的扩展支持实在不够良好,弄了半天没弄出来),若后续有更好的移动端web头像上传插件,可考虑后续替代升级。
demo主要实现的关键功能: 图像的方向修正及图像截取
虽然没有实现图像拖拽和双指缩放,但其缩放后的相对于图像的比例计算和拖拽坐标计算规则是一致的,可以参考。同时图像的旋转功能也可参考其中的核心代码。(对于图像旋转及坐标计算真的是一个大坑,特别在移动端页面代码的调试困难比较大,主要来自于无法详尽地获取移动端页面的控制台信息而导致的。。目前的移动端web远程调试技术又仅限于windows+安卓机或mac+iphone,对于我这种windows+iphone的真是伤不起啊、、只能靠alert、、、)。以下demo代码可以说不算长。但其中某些坑爬了很久才爬出来。。有点小郁闷。
好,简单地介绍下截取图像及方向旋转吧。
首先我们的思路是利用Jcrop和canvas在前台完成图像截取,利用exif获得图像方向信息再利用canvas旋转图像至正确方向,随后把处理完后的图像由canvas转成base64传给后台,再存为图片文件。具体步骤:
1、通过input调用相机或相册,在app内获取到用户想要上传的源图像;
2、读取图像方向信息,以便于对图像进行截取区域的坐标转换,如下图例:
如上图,绿色为图像原本(真实)方向,红色为图像应该展示的方向;所以当我们取图像左上角区域的时候,实际上应该取图片真实方向的左下角;因为img标签它自己会修复展示的方向,但是实际图片的存放方向仍如绿色区域所示,所以如果我们直接将红色区域的坐标用在img中进行截取时,就会存在截取区域对应不上的问题;所以我们要进行坐标转换。
3、利用canvas获取截取区域图像数据信息,进行坐标转换后画入canvas中去;
4、进行方向修正,利用 canvas的rotate方法将已截取的图像旋转至正确方向,再重新画入canvas中;
5、从canvas中获取正确图像的base64码,并传给后台。
以下是分布代码展示:
h5部分:
在页面上画出一个span区域和一个隐藏的input:
1 <div class="header-img"> 2 <span id="uploadImg"><!-- 自定义头像上传 --> 3 </span> 4 <input accept="image/gif,image/jpeg,image/jpg,image/png,image/bmp" type="file" style="display:none;" id="uploadImgInput"> 5 <img id="myimg" src="${ctx }/resource/wo/img/man_1.png"> 6 </div>
js部分:
用户点击span的时候手动触发input的click事件来调用相册或相机:
1 //头像上传 2 $("#uploadImg").on('click',function(){ 3 //input file的change事件只能触发一次,因此每次点击头像后都将原input绑定的事件解除,重新赋值并绑定。 4 $("#uploadImgInput").off('change'); 5 $('#uploadImgInput').val(''); 6 $("#uploadImgInput").on('change',uploadImgInputChangeHandler); 7 $("#uploadImgInput").click();//手动触发input点击事件 8 }); 9 //头像上传点击事件触发方法 10 function uploadImgInputChangeHandler(){ 11 var input = this; 12 if (input.files && input.files[0]) { 13 var reader = new FileReader(); 14 reader.readAsDataURL(input.files[0]); //获取选中的第一个图片文件 15 //获取照片方向角属性,也可用于用户旋转控制 16 EXIF.getData(input.files[0], function() { 17 // alert(EXIF.pretty(this)); 18 EXIF.getAllTags(this); 19 //alert("Orientation:"+EXIF.getTag(this, 'Orientation')); 20 pageData.Orientation = EXIF.getTag(this, 'Orientation'); //将方向信息存入对象中 21 //return; 22 }); 23 reader.onload = function (e) { 24 $("#displayImgDiv").show(); 25 $("#popupContent").hide(); 26 $('#displayImg').attr('src', e.target.result); 27 $('#displayImg')[0].onload = function(){ 28 //调用Jcrop进行图像截取,具体参数配置可查看相关教程 29 $('#displayImg').Jcrop( { 30 allowSelect: true, 31 allowMove: true, 32 allowResize: true, 33 setSelect: [ 10, 10, screenWidth-10, screenWidth-10], 34 aspectRatio: 1 , 35 minSize: [200, 200 ], 36 maxSize: [screenWidth, screenWidth ], 37 dragEdges: true, 38 onSelect: updateCoords,//截取框每次移动后的调用的方法 39 trackDocument: false 40 } ,function(){ 41 pageData.jcropApi = this;//将jcrop对象存入全局变量中,以便后续获取 42 } 43 ); 44 } 45 } 46 } 47 } 48 49 //jcrop选框选择后处理事件 50 function updateCoords(c){ 51 var img = document.getElementById("displayImg"); 52 var canvas = document.createElement('canvas'); 53 var ctx = canvas.getContext("2d"); 54 var imgW_ori = eval(img.width);//无视方向后的img标签中图片的原始方向角度的宽 55 var imgH_ori = eval(img.height);//无视方向后的img标签中图片的原始方向角度的高 56 var imgW_css = $(img).width(); 57 var imgH_css = $(img).height(); 58 //修复手机图像方向问题 59 //1、坐标确定:由于img标签在浏览器中会根据Orientation属性自动调整展示方向 60 //故修复方向实际上则是根据当前图像截取框与伪修复图像整体的位置,判断应该截取得区域于真实图片中的位置关系后, 61 //对相关数据进行处理,canvas的drawImage一共有9个传参,其中需要根据方向调整的有4个: 62 var sx,sy,sw,sh,wRatio,hRatio,tx,ty,degree; 63 var Orientation = eval(pageData.Orientation); 64 if(Orientation != 1 && typeof(Orientation)!="undefined" && Orientation != "" ){ 65 //如果方向角不为1,都需要进行旋转 66 switch(Orientation){ 67 case 6://需要顺时针(向左)90度旋转 68 //对于图片缩放过后,需获取图片实际缩放比例 69 wRatio = imgW_ori/imgH_css; 70 hRatio = imgH_ori/imgW_css; 71 //alert(imgW_ori+","+imgH_ori+","+imgW_css+","+imgH_css); 72 sx = c.y*wRatio; 73 sy = (imgW_css-c.x-c.w)*hRatio; 74 sw = c.h*wRatio; 75 sh = c.w*hRatio; 76 degree = 90 * Math.PI / 180; 77 tx = 0; 78 ty = -200; 79 /* tx = 0; 80 ty = -imgH_ori; 81 canvas.width = imgH_ori; 82 canvas.height = imgW_ori; */ 83 break; 84 case 8://需要逆时针(向右)90度旋转 85 wRatio = imgW_ori/imgH_css; 86 hRatio = imgH_ori/imgW_css; 87 sx = (imgH_css-c.y-c.h)*wRatio; 88 sy = c.x*hRatio; 89 sw = c.h*wRatio; 90 sh = c.w*hRatio; 91 degree = 3 * 90 * Math.PI / 180; 92 tx = -200; 93 ty = 0; 94 /* tx = -imgW_ori; 95 ty = 0; 96 canvas.width = imgH_ori; 97 canvas.height = imgW_ori; */ 98 break; 99 case 3://需要180度旋转 100 wRatio = imgW_ori/imgW_css; 101 hRatio = imgH_ori/imgH_css; 102 sx = (imgW_css-c.x-c.w)*wRatio; 103 sy = (imgH_css-c.y-c.h)*hRatio; 104 sw = c.w*wRatio; 105 sh = c.h*hRatio; 106 degree = Math.PI; 107 tx = -200; 108 ty = -200; 109 /*tx = -imgW_ori; 110 ty = -imgH_ori; 111 canvas.width = imgW_ori; 112 canvas.height = imgH_ori; */ 113 break; 114 } 115 }else{ 116 wRatio = imgW_ori/imgW_css; 117 hRatio = imgH_ori/imgH_css; 118 sx = c.x*wRatio; 119 sy = c.y*hRatio; 120 sw = c.w*wRatio; 121 sh = c.h*hRatio; 122 tx = 0; 123 ty = 0; 124 } 125 var tempImage = new Image();//新建一个image对象,用于存放不带exif信息的图像数据,新建image的原因是若直接使用原来的image对象,会有图片展示区域与真实区域错位不一致从而导致无法获取正确的图像源数据 126 tempImage.src = img.src; 127 tempImage.onload = function(){//确保图像加载完毕后再进行下一步,不然可能会导致获取不到图片宽高 128 //将canvas画布的大小设为200x200大小并裁剪成圆形作为头像 129 canvas.width = 200; 130 canvas.height = 200; 131 ctx.arc(100, 100, 100, 0 ,2*Math.PI); 132 ctx.clip(); 133 //alert("Orientation:"+Orientation+","+sx+","+sy+","+sw+","+sh+","+tx+","+ty+","+degree); 134 ctx.drawImage(tempImage, sx, sy, sw, sh, 0, 0, 200, 200); //在canvas中将裁剪后图片重新画出来 135 //ctx.drawImage(img, 0, 0, 3024, 4032, 0, 0, 200, 200); 136 var base64 = canvas.toDataURL('image/jpeg',0.5); 137 //window.open(base64); 138 //2、接下来进行旋转 139 var resultImg = new Image();//新建一个image对象,用于存放截图后的数据 140 resultImg.src = base64; 141 resultImg.onload = function(){ 142 if(degree){ 143 ctx.rotate(degree);//对方向进行修正 144 } 145 ctx.drawImage(resultImg, tx, ty); 146 pageData.base64 = canvas.toDataURL('image/jpeg');//将base64码存入全局变量中 147 //window.open(pageData.base64); 148 } 149 } 150 }
这样就拿到了每次截取后的截取区域的正确base64码,当用户点击确定的时候将该数据传给后台即可;
这种前台截取得方式适合用于图像清晰度要求低,否则图像太大还是建议使用输入输出流传给后台后再进行图像处理;优点是前台直接完成了图像处理工作,减轻了服务器的压力,同时能用js实现的为什么要麻烦后台呢对吧。
水平有限,以上经验仅供参考,若有不同意见或建议欢迎一起讨论和学习~