H5图片上传应用开发中的那些坑……
图片上传是web应用开发中非常常见的功能,在桌面应用开发中,我们有很多插件,比如jQuery File Upload、uploadify,但是在移动端,jQuery太重,而且我们又有许多HTML5的API可以使用,所以,在新的项目里,选择了使用HTML5的file API来实现图片上传的功能。但是,坑也不少啊/(ㄒoㄒ)/~~
1. 项目介绍
项目中需要上传图片,作为头像或者作为文章的图片。因为没有使用jQuery,也没有使用zepto,而且使用场景只是在移动端,因此选择了不使用插件,使用HTML5的file API。
2. 使用方法
file API的使用方法很简单,可以拆解为以下几步:
- 在HTML中添加一个input组件,type类型设置为file。
-
监听input的change事件。
-
在事件处理函数中,用FileReader api读取图片内容。
-
监听FileReader对象的onloadend事件,当读取文件完成时,上传图片。
3. 坑们
1. ios下alert弹框会挂
在选择图片后,首先判断图片的类型是否在限定的类型内、图片大小是否超出限定大小,如果超出,则弹出弹框提示用户。结果,在ios7下,浏览器、webview都会挂,在ios9下,webview会挂,而浏览器中是好的。
这个问题,之前都没有碰到过,而且也不好调试,Google之后发现了一篇文章,解释是处理JS的线程与处理UIAlertView的线程发生了死锁,导致alert弹框无法被关闭1。解决的办法倒是很简单,调用alert的时候,放到setTimeout内调用即可。
2. 图片压缩
随着手机摄像头分辨率越来越高,单张图片的大小也是水涨船高,而过大的图片,对于手机处理能力和网络来说,也是压力山大啊。因此,在上传图片之前,对图片进行压缩也是必须的了。
而在ios下,通过canvas对图片进行压缩时,图片被压扁了,如下图所示:
解决方法:通过Alpha通道获取图片被压缩后的高度,获取压缩比,再取压缩后的图片,重绘到正确高度的canvas上。
具体获取被压缩后图片高度的代码如下:
/** * http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios */ /** * Detecting vertical squash in loaded image. * Fixes a bug which squash image vertically while drawing into canvas for some images. * This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel * */ function detectVerticalSquash(img) { var iw = img.naturalWidth, ih = img.naturalHeight;//获取图片真实高度和宽度 var canvas = document.createElement('canvas'); canvas.width = 1;//绘制一个一像素宽,和原图登高的新图片 canvas.height = ih; var ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); var data = ctx.getImageData(0, 0, 1, ih).data; // search image edge pixel position in case it is squashed vertically. var sy = 0; var ey = ih; var py = ih; while (py > sy) { var alpha = data[(py - 1) * 4 + 3];//读取图片的Alpha通道的数据 if (alpha === 0) { ey = py; } else { sy = py; } py = (ey + sy) >> 1; } var ratio = (py / ih);//获取到的py即是压缩后的高度,ratio即是压缩比 return (ratio===0)?1:ratio; }
在绘制新图片时,可以这样操作:
var vertSquashRatio = detectVerticalSquash(img); document.createElement('canvas').getContext('2d').drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight * vertSquashRatio, 0, 0, width, height);
这里,简单介绍下CanvasRenderingContext2D.drawImage()方法,具体的语法为:
void ctx.drawImage(image, dx, dy); void ctx.drawImage(image, dx, dy, dWidth, dHeight); void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
其中,dx,dy表示绘图的起点距离画布的左上角的距离,dWidth,dHeight表示在画布上绘制的宽度和高度;sx和sy表示原图被截取的画面的左上角坐标,sWidth,sHeight表示原图被截取画面的宽高,下面的图更直观得做了介绍:
因此,上面这段代码的意思就是从目标画布的左上角开始绘制,绘制一个width宽,height高的图片(width和height可以为压缩后的宽高),绘制的图片源为图片的左上角开始,截取的宽度为图片的真实宽度,截取的高度为图片的高度乘以压缩比。
3. 图片方向不正确
用手机拍摄的照片有不同的方向,而这些信息保存在图片的exif信息中。在手机相册里可以正确显示,但是上传后竖屏拍摄的图片会变横屏。
解决方法:
先用exif.js获取图片的Orientation信息,再根据方向信息进行旋转调整。
图片的Orientation值代表的图片相位对应关系如下图所示:
调整的关键代码如下:
switch(Orientation){ case 1://不需要旋转 rotateImg(image, 0, canvas); break; case 6://需要顺时针(向左)90度旋转 rotateImg(image, 1, canvas); break; case 3://需要顺时针(向左)180度旋转 rotateImg(image, 2, canvas); break; case 8://需要顺时针(向左)270度旋转 rotateImg(image, 3, canvas); break; } function rotateImg(img, step,canvas) { if (img == null)return; var width = img.width; var height = img.height; step = step ? step : 0; var degree = step * 90 * Math.PI / 180; var ctx = canvas.getContext('2d'); var vertSquashRatio = detectVerticalSquash(img); switch (step) { case 1: canvas.width = height; canvas.height = width; ctx.translate(height, 0); ctx.rotate(degree); break; case 2: canvas.width = width; canvas.height = height; ctx.translate(width, height); ctx.rotate(degree); break; case 3: canvas.width = height; canvas.height = width; ctx.translate(0, width); ctx.rotate(degree); break; } ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight * vertSquashRatio, 0, 0, width, height); }
4. IOS7下的Safari,服务器返回304时会显示白屏
好吧,这个问题前端表示无能为力,只能通过设置服务器不返回304来解决了,具体情况可以看这里。
参考链接: