移动端图片上传旋转、压缩问题的方法


Posted in Javascript onOctober 16, 2018

前言

在手机上通过网页 input 标签拍照上传图片,有一些手机会出现图片旋转了90度d的问题,包括 iPhone 和个别三星手机。这些手机竖着拍的时候才会出现这种问题,横拍出来的照片就正常显示。因此,可以通过获取手机拍照角度来对照片进行旋转,从而解决这个问题。

Orientation

这个参数并不是所有图片都有的,不过手机拍出来的图片是带有这个参数的。

旋转角度 参数值
1
顺时针90° 6
逆时针90° 8
180° 3

参数为 1 的时候显示正常,那么在这些横拍显示正常,即 Orientation = 1 的手机上,竖拍的参数为 6。

想要获取 Orientation 参数,可以通过 exif.js 库来操作。exif.js 功能很多,体积也很大,未压缩之前足足有 30k,这对手机页面加载来说是非常大影响的。而我只需要获取 Orientation 信息而已,所以我这里删减了 exif.js 库的一些代码,将代码缩小到几KB。

exif.js 获取 Orientation :

EXIF.getData(file, function() { 
 var Orientation = EXIF.getTag(this, 'Orientation');
});

file 则是 input 文件表单上传的文件。上传的文件经过 fileReader.readAsDataURL(file) 就可以实现预览图片了,这方面不清楚的可以查看:HTML5 进阶系列:文件上传下载

旋转

旋转需要用到 canvas 的 rotate() 方法。

ctx.rotate(angle);

rotate 方法的参数为旋转弧度。需要将角度转为弧度:degrees * Math.PI / 180

旋转的中心点默认都在 canvas 的起点,即 ( 0, 0 )。旋转的原理如下图:

移动端图片上传旋转、压缩问题的方法

旋转之后,如果从 ( 0, 0 ) 点进行 drawImage(),那么画出来的位置就是在左图中的旋转 90 度后的位置,不在可视区域呢。旋转之后,坐标轴也跟着旋转了,想要显示在可视区域呢,需要将 ( 0, 0 ) 点往 y 轴的反方向移 y 个单位,此时的起始点则为 ( 0, -y )。

同理,可以获得旋转 -90 度后的起始点为 ( -x, 0 ),旋转 180 度后的起始点为 ( -x, -y )。

压缩

手机拍出来的照片太大,而且使用 base64 编码的照片会比原照片大,那么上传的时候进行压缩就非常有必要的。现在的手机像素这么高,拍出来的照片宽高都有几千像素,用 canvas 来渲染这照片的速度会相对比较慢。

因此第一步需要先对上传照片的宽高做限制,判断宽度或高度是否超出哪个范围,则等比压缩其宽高。

var ratio = width / height;if(imgWidth > imgHeight && imgWidth > xx){
 imgWidth = xx;
 imgHeight = Math.ceil(xx / ratio);
}else if(imgWidth < imgHeight && imgHeight > yy){
 imgWidth = Math.ceil(yy * ratio);
 imgHeight = yy;
}

第二步就通过 canvas.toDataURL() 方法来压缩照片质量。

canvas.toDataURL("image/jpeg", 1);

toDataURL() 方法返回一个包含图片展示的 data URI 。使用两个参数,第一个参数为图片格式,默认为 image/png。第二个参数为压缩质量,在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。

总结

综合以上,例子的代码包括精简的exif.js库地址:file-demo

主要的核心代码如下:

<input type="file" id="files" ><img src="blank.gif" id="preview">
<script src="small-exif.js"></script>
<script>
 var ipt = document.getElementById('files'),
 img = document.getElementById('preview'),
 Orientation = null;
 ipt.onchange = function () { 
  var file = ipt.files[0],
 reader = new FileReader(),
 image = new Image(); 
 if(file){
  EXIF.getData(file, function() { 
   Orientation = EXIF.getTag(this, 'Orientation');
  });
  reader.onload = function (ev) {
   image.src = ev.target.result;
   image.onload = function () {  
    var imgWidth = this.width,
    imgHeight = this.height;    // 控制上传图片的宽高 
    if(imgWidth > imgHeight && imgWidth > 750){
     imgWidth = 750;
     imgHeight = Math.ceil(750 * this.height / this.width);
    }else if(imgWidth < imgHeight && imgHeight > 1334){
     imgWidth = Math.ceil(1334 * this.width / this.height);
     imgHeight = 1334;
    }    
    var canvas = document.createElement("canvas"),
    ctx = canvas.getContext('2d');
    canvas.width = imgWidth;
    canvas.height = imgHeight; 
    if(Orientation && Orientation != 1){
     switch(Orientation){      
     case 6: // 旋转90度
      canvas.width = imgHeight; 
      canvas.height = imgWidth; 
      ctx.rotate(Math.PI / 2);       
      // (0,-imgHeight) 从旋转原理图那里获得的起始点
      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, imgWidth, imgHeight);
    }
    img.src = canvas.toDataURL("image/jpeg", 0.8);
   }
  }
  reader.readAsDataURL(file);
 }
}</script>
Javascript 相关文章推荐
通过jquery还原含有rowspan、colspan的table的实现方法
Feb 10 Javascript
jQuery解决下拉框select设宽度时IE 6/7/8下option超出显示不全
May 27 Javascript
JQuery设置时间段下拉选择实例
Dec 30 Javascript
浅谈javascript中的DOM方法
Jul 16 Javascript
jQuery插件实现静态HTML验证码校验
Nov 06 Javascript
JQuery+EasyUI轻松实现步骤条效果
Feb 22 Javascript
使用bootstrap实现多窗口和拖动效果
Sep 22 Javascript
jQuery Validate 数组 全部验证问题
Jan 12 Javascript
js实现QQ面板拖拽效果(慕课网DOM事件探秘)(全)
Sep 19 Javascript
详解tween.js 中文使用指南
Jan 05 Javascript
微信小程序实现YDUI的ScrollTab组件
Feb 02 Javascript
JS浮点数运算结果不精确的Bug解决
Aug 01 Javascript
JavaScript实现表单注册、表单验证、运算符功能
Oct 15 #Javascript
从零开始搭建vue移动端项目到上线的步骤
Oct 15 #Javascript
jQuery轻量级表单模型验证插件
Oct 15 #jQuery
详解ESLint在Vue中的使用小结
Oct 15 #Javascript
手淘flexible.js框架使用和源代码讲解小结
Oct 15 #Javascript
javascript匿名函数中的'return function()'作用
Oct 15 #Javascript
Vue Cli3 创建项目的方法步骤
Oct 15 #Javascript
You might like
PHP与javascript对多项选择的处理
2006/10/09 PHP
PHP 异步执行方法,模拟多线程的应用分析
2013/06/03 PHP
php include和require的区别深入解析
2013/06/17 PHP
解析PHP函数array_flip()在重复数组元素删除中的作用
2013/06/27 PHP
php网站地图生成类示例
2014/01/13 PHP
CodeIgniter输出中文乱码的两种解决办法
2014/06/12 PHP
PHP中把对象转换为关联数组代码分享
2015/04/09 PHP
搭建基于Docker的PHP开发环境的详细教程
2015/07/01 PHP
php运行报错Call to undefined function curl_init()的最新解决方法
2016/11/20 PHP
smarty模板数学运算示例
2016/12/11 PHP
PHP使用递归按层级查找数据的方法
2019/11/10 PHP
PHP Ajax跨域问题解决方案代码实例
2020/08/01 PHP
Jquery实现页面加载时弹出对话框代码
2013/04/19 Javascript
JS字符串截取函数实例
2013/12/27 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
2014/09/02 Javascript
使用javaScript动态加载Js文件和Css文件
2015/10/24 Javascript
JavaScript中函数声明与函数表达式的区别详解
2016/08/18 Javascript
bootstrap-datetimepicker实现只显示到日期的方法
2016/11/25 Javascript
基于构造函数的五种继承方法小结
2017/07/27 Javascript
JavaScript在web自动化测试中的作用示例详解
2019/08/25 Javascript
微信小程序实现一张或多张图片上传(云开发)
2019/09/25 Javascript
vue实现网络图片瀑布流 + 下拉刷新 + 上拉加载更多(步骤详解)
2020/01/14 Javascript
JS实现音量控制拖动
2020/01/15 Javascript
python测试驱动开发实例
2014/10/08 Python
在Python的框架中为MySQL实现restful接口的教程
2015/04/08 Python
python机器学习包mlxtend的安装和配置详解
2019/08/21 Python
python Xpath语法的使用
2020/11/26 Python
泰国在线书店:SE-ED
2020/06/21 全球购物
一套Delphi的笔试题一
2016/02/14 面试题
初中军训感想300字
2014/03/05 职场文书
文秘应届生求职信
2014/07/05 职场文书
运输企业安全生产责任书
2014/07/28 职场文书
2014年驻村干部工作总结
2014/11/17 职场文书
自我工作评价范文
2015/03/06 职场文书
幼儿园庆六一主持词
2015/06/30 职场文书
oracle覆盖导入dmp文件的2种方法
2021/05/21 Oracle