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


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 相关文章推荐
sogou地图API用法实例教程
Sep 11 Javascript
浅析JavaScript事件和方法
Feb 28 Javascript
JavaScript中常见获取元素的方法汇总
Mar 04 Javascript
深入分析下javascript中的[]()+!
Jul 07 Javascript
浅析JavaScript中的变量复制、参数传递和作用域链
Jan 13 Javascript
jquery.validate[.unobtrusive]和Bootstrap实现tooltip错误提示问题分析
Oct 30 Javascript
bootstrapValidator表单验证插件学习
Dec 30 Javascript
详解基于webpack和vue.js搭建开发环境
Apr 05 Javascript
用JavaScript做简易的购物车的代码示例
Oct 20 Javascript
微信小程序使用toast消息对话框提示用户忘记输入用户名或密码功能【附源码下载】
Dec 09 Javascript
JS重学系列之聊聊new操作符
Mar 04 Javascript
JavaScript实现网页留言板功能
Nov 23 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实现异步操作的研究
2013/02/03 PHP
php session劫持和防范的方法
2013/11/12 PHP
php Imagick获取图片RGB颜色值
2014/07/28 PHP
Laravel框架实现发送短信验证功能代码
2016/06/06 PHP
php 开发中加密的几种方法总结
2017/03/22 PHP
Jquery 表单取值赋值的一些基本操作
2009/10/11 Javascript
jQuery学习笔记[1] jQuery中的DOM操作
2010/12/03 Javascript
js动态生成指定行数的表格
2013/07/11 Javascript
Document:getElementsByName()使用方法及示例
2013/10/28 Javascript
JS继承用法实例分析
2015/02/05 Javascript
JavaScript中SetInterval与setTimeout的用法详解
2015/11/10 Javascript
JavaScript html5 canvas画布中删除一个块区域的方法
2016/01/26 Javascript
canvas实现手机端用来上传用户头像的代码
2016/10/20 Javascript
Vue2 监听属性改变watch的实例代码
2018/08/27 Javascript
vue以组件或者插件的形式实现throttle或者debounce
2019/05/22 Javascript
在vue中使用jsx语法的使用方法
2019/09/30 Javascript
浅谈vue中document.getElementById()拿到的是原值的问题
2020/07/26 Javascript
Vue的v-model的几种修饰符.lazy,.number和.trim的用法说明
2020/08/05 Javascript
[51:34]Ti4主赛事胜者组 DK vs EG 2
2014/07/19 DOTA
浅谈Matplotlib简介和pyplot的简单使用——文本标注和箭头
2018/01/09 Python
Python采集代理ip并判断是否可用和定时更新的方法
2018/05/07 Python
Python之批量创建文件的实例讲解
2018/05/10 Python
tensorflow实现逻辑回归模型
2018/09/08 Python
实例详解python函数的对象、函数嵌套、名称空间和作用域
2019/05/31 Python
Python操作SQLite数据库过程解析
2019/09/02 Python
Python元组 tuple的概念与基本操作详解【定义、创建、访问、计数、推导式等】
2019/10/30 Python
Python转换itertools.chain对象为数组的方法
2020/02/07 Python
Python+OpenCV检测灯光亮点的实现方法
2020/11/02 Python
First Aid Beauty官网:FAB急救面霜
2018/05/24 全球购物
高级3D打印市场:Gambody
2019/12/26 全球购物
优秀员工自荐书
2013/12/19 职场文书
平面设计专业大学生职业规划书
2014/03/12 职场文书
买房协议书
2014/04/11 职场文书
村当支部个人对照检查材料思想汇报
2014/10/06 职场文书
导游词之永济鹳雀楼
2020/01/16 职场文书
Canvas如何做个雪花屏版404的实现
2021/09/25 HTML / CSS