H5图片上传应用开发中的那些坑……

Author : lovecicy

图片上传是web应用开发中非常常见的功能,在桌面应用开发中,我们有很多插件,比如jQuery File Upload、uploadify,但是在移动端,jQuery太重,而且我们又有许多HTML5的API可以使用,所以,在新的项目里,选择了使用HTML5的file API来实现图片上传的功能。但是,坑也不少啊/(ㄒoㄒ)/~~

1. 项目介绍

项目中需要上传图片,作为头像或者作为文章的图片。因为没有使用jQuery,也没有使用zepto,而且使用场景只是在移动端,因此选择了不使用插件,使用HTML5的file API。

2. 使用方法

file API的使用方法很简单,可以拆解为以下几步:

  1. 在HTML中添加一个input组件,type类型设置为file。

  2. 监听input的change事件。

  3. 在事件处理函数中,用FileReader api读取图片内容。

  4. 监听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表示原图被截取画面的宽高,下面的图更直观得做了介绍:

Canvas_drawimage.jpg

因此,上面这段代码的意思就是从目标画布的左上角开始绘制,绘制一个width宽,height高的图片(width和height可以为压缩后的宽高),绘制的图片源为图片的左上角开始,截取的宽度为图片的真实宽度,截取的高度为图片的高度乘以压缩比。

3. 图片方向不正确

用手机拍摄的照片有不同的方向,而这些信息保存在图片的exif信息中。在手机相册里可以正确显示,但是上传后竖屏拍摄的图片会变横屏。

解决方法:

先用exif.js获取图片的Orientation信息,再根据方向信息进行旋转调整。

图片的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来解决了,具体情况可以看这里

参考链接:

  1. Accessing JPEG EXIF rotation data in JavaScript on the client side
  2. HTML5 Canvas drawImage ratio bug iOS
  3. H5拍照应用开发经历的那些坑儿
  4. 利用exif.js解决ios手机上传竖拍照片旋转90度问题
  5. Why can’t I close or dismiss a Javascript alert in UIWebView?
  6. IOS7-BUG: SHOWS WHITE PAGE WHEN GETTING 304 NOT MODIFIED FROM SERVER
standard

Have your say