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


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 相关文章推荐
一个收集图片的bookmarlet(js 刷新页面中的图片)
May 27 Javascript
jquery.tmpl JQuery模板插件
Oct 10 Javascript
jquery Moblie入门—hello world的示例代码学习
Jan 08 Javascript
JS中的数组的sort方法使用示例
Jan 22 Javascript
再谈javascript原型继承
Nov 10 Javascript
JavaScript学习笔记之DOM基础 2.4
Aug 14 Javascript
深入理解JavaScript程序中内存泄漏
Mar 17 Javascript
jQuery实现可拖拽的许愿墙效果【附demo源码下载】
Sep 14 Javascript
js微信扫描二维码登录网站技术原理
Dec 01 Javascript
AngulaJS路由 ui-router 传参实例
Apr 28 Javascript
vue服务端渲染添加缓存的方法
Sep 18 Javascript
js实现文章目录索引导航(table of content)
May 10 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
解析thinkphp import 文件内容变量失效的问题
2013/06/20 PHP
getJSON跨域SyntaxError问题分析
2014/08/07 PHP
php mysql PDO 查询操作的实例详解
2017/09/23 PHP
PHP7新增函数
2021/03/09 PHP
jQuery chili图片远处放大插件
2009/11/30 Javascript
javascript XMLHttpRequest对象全面剖析
2010/04/24 Javascript
一个封装js代码-----展开收起效果示例
2013/07/03 Javascript
js 调用父窗口的具体实现代码
2013/07/15 Javascript
JavaScript异步编程:异步数据收集的具体方法
2013/08/19 Javascript
js取值中form.all和不加all的区别介绍
2014/01/20 Javascript
Extjs grid panel自带滚动条失效的解决方法
2014/09/11 Javascript
AngularJS学习笔记之ng-options指令
2015/06/16 Javascript
简单理解JavaScript中的封装与继承特性
2016/03/19 Javascript
jquery层级选择器的实现(匹配后代元素div)
2016/09/05 Javascript
如何处理JSON中的特殊字符
2016/11/30 Javascript
jQuery密码强度验证控件使用详解
2017/01/05 Javascript
jQuery基于ajax实现页面加载后检查用户登录状态的方法
2017/02/10 Javascript
bootstrap表单示例代码分享
2017/05/18 Javascript
JS判断非空至少输入两个字符的简单实现方法
2017/06/23 Javascript
js 原生判断内容区域是否滚动到底部的实例代码
2017/11/15 Javascript
node事件循环和process模块实例分析
2020/02/14 Javascript
[14:03]2017DOTA2亚洲邀请赛开幕式:12神兵演绎水墨中华
2017/04/01 DOTA
如何使用Flask-Migrate拓展数据库表结构
2019/07/24 Python
python制作朋友圈九宫格图片
2019/11/03 Python
Python生成器常见问题及解决方案
2020/03/21 Python
python切割图片的示例
2020/11/12 Python
CSS实现fullpage.js全屏滚动效果的示例代码
2021/03/24 HTML / CSS
毕业生求职简历中的自我评价
2013/10/18 职场文书
制衣厂各岗位职责
2013/12/02 职场文书
安全保证书范文
2014/04/29 职场文书
2014国庆节幼儿园亲子活动方案
2014/09/16 职场文书
学生检讨书范文
2015/01/27 职场文书
医生个人年终总结
2015/02/28 职场文书
2015公司年度工作总结
2015/05/14 职场文书
创业计划书之健康营养产业
2019/10/15 职场文书
Go微服务项目配置文件的定义和读取示例详解
2022/06/21 Golang