移动端图片上传的那些事儿
宿主环境:微信公众号
应公司业务需要,需在公众号上增加一个人脸采集的入口,核心是图片的上传和预览。
拿到需求后在心中默默的将整个实现的思路缕了一遍。既然是在微信公众号上面开发,那么调用微信sdk提供的chooseImage和previewImage两个方法便能轻松实现这个功能。我只需要将chooseImage的回调中返回的serverId传给后台,让他们根据serverId去微信服务器下载并上传至数据库就行。这一系列图片上传的事情相当于都是“关我屁事”了..
内心一阵窃喜,心想着这个这个功能应该个把小时可以搞定,搞完又可以在线划水了,开心~
a few minutes later~
后台大佬跟我说,通过拿serverId去微信服务器下载图片需要进行一系列的验证,并且本次上传的图片是直接上传至第三方服务器。因此需要我以文件流的形式传给他们。
为了响应后台大佬需要,我调整了下实现方式,将chooseImage回调中返回的localImgData转换成文件(该字段的返回值就是当前图片对应的base64的地址,忍不住要给wx点赞,解放生产力啊。。),并以表单的方式传给后台,大功告成。
核心代码如下:
1 function convertBase64UrlToBlob(urlData) { 2 let bytes = window.atob(urlData.split(',')[1]); 3 let ab = new ArrayBuffer(bytes.length); 4 let ia = new Uint8Array(ab); 5 for (let i = 0; i < bytes.length; i++) { 6 ia[i] = bytes.charCodeAt(i) 7 } 8 return new Blob([ab], {type: 'image/jpeg'}); 9 }
功能临近上线,测试大佬的一句:“安卓机不行啊,图片上传时一直在转圈圈..”。简直是晴天霹雳,只能强装镇定,打开调试工具,查起了问题原因。发现安卓机上chooseImag返回的localImgData压根不是base64,而是微信服务器上一串特定标识的字符串,与IOS下的完全不一致,简直超乎我的想象。(我要把我上面给wx的点赞收回..)
迫于无奈,我只能用回最原始的方式了,使用 input 来实现选择图片,图片预览的话还是使用微信sdk提供的previewImage;
1 function changeFile(ev) { 2 let reader = new FileReader(); 3 let file = ev.target.files[0]; 4 5 reader.readAsDataURL(file); 6 reader.onload = () => { 7 this.imageUrl = reader.result; 8 } 9 }
当我正想安安静静的做做其他事情,忘记上面这个功能时,产品大佬跟我说,有客户反馈,选完图片后,呈现出来的图片被旋转了,我... 真所谓是一波三折...
由于之前没有处理过类似的问题,因此只能硬着头皮研究起了各种解决方案。在github上发现有个javascript库,用于读取图片的元数据,元数据中包括Orientation(旋转角度),正是我想要的,因此基于该工具类重新开始改造起了我的代码。
1、首先写了一个工具方法,用于获取图片被旋转的角度。
function getOrientation(file) { return new Promise((resolve, reject) => { EXIF.getData(file, function () { let Orientation = EXIF.getTag(this, "Orientation"); resolve(Orientation) }); }) }
2、其次,根据被旋转的角度,使用canvas绘制并生成图片。该方法中一并解决了图片过大上传至服务器时,接口响应很慢的问题。
function compressImg({ img, type = "image/jpeg", mx = "750", mh = "750", orientation = 1 } = opts) { return new Promise((resolve, reject) => { const { width: originWidth, height: originHeight } = img; // 最大尺寸限制 const maxWidth = mx; // 目标尺寸 let targetWidth = originWidth; let targetHeight = originHeight; if (originWidth > maxWidth) { // 宽图片 targetWidth = maxWidth; targetHeight = Math.round(maxWidth * (originHeight / originWidth)); } // 创建画布 const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); // 图片绘制 // context.clearRect(0, 0, targetWidth, targetHeight); if (!window.imgIsRotate) { switch (orientation) { case 6: // 旋转90度 canvas.width = targetHeight; canvas.height = targetWidth; context.rotate(Math.PI / 2); // (0,-targetHeight) 从旋转原理图那里获得的起始点 context.drawImage(img, 0, -targetHeight, targetWidth, targetHeight); break; case 3: // 旋转180度 canvas.width = targetWidth; canvas.height = targetHeight; context.rotate(Math.PI); context.drawImage(img, -targetWidth, -targetHeight, targetWidth, targetHeight); break; case 8: // 旋转-90度 canvas.width = targetHeight; canvas.height = targetWidth; context.rotate(3 * Math.PI / 2); context.drawImage(img, -targetWidth, 0, targetWidth, targetHeight); break; default: canvas.width = targetWidth; canvas.height = targetHeight; context.drawImage(img, 0, 0, targetWidth, targetHeight); } } else { canvas.width = targetWidth; canvas.height = targetHeight; context.drawImage(img, 0, 0, targetWidth, targetHeight); } canvas.toBlob(function (blob) { resolve(blob); }, type || "image/jpeg", 0.92); }); }
至此,图片的选择和预览算是真正的告一段落了。
通过本次实践,从发现问题到解决问题的这一过程,对自己来说,收获还是很多的,希望再接再厉吧!
注:本文中如果有描述不当或者有偏差的地方,希望各位大哥见谅并指正,大家一起加油。