关于前端上传文件全面基础扫盲贴六-图片上传旋转重绘预览等实战附DEMO

40次阅读

共计 5321 个字符,预计需要花费 14 分钟才能阅读完成。

系列文章

关于前端上传文件全面基础扫盲贴 (零)
关于前端上传文件全面基础扫盲贴 (一) —– XMLHttpRequest
关于前端上传文件全面基础扫盲贴 (二) —– File
关于前端上传文件全面基础扫盲贴 (三) —– FormData
关于前端上传文件全面基础扫盲贴 (四) —– FileReader
关于前端上传文件全面基础扫盲贴 (五) —– H5 拖拽事件
关于前端上传文件全面基础扫盲贴(六) —– 图片上传, 旋转, 重绘, 预览等实战(附 DEMO)

图片上传, 旋转, 重绘, 预览等实战

距离我上次写的系列文章好像都过了半年了, 废话不多说, 先来展示一下技术思路

一、获取用户照片数据

  1. 获取用户摄像头图片, 不是所有手机支持, 并且部分手机会有旋转角度的问题;
  2. Input 控件获取照片文件。

二、编辑合成照片

  1. 使用 canvas 编辑压缩, 重设尺寸比例;
  2. 转成 base64 输出预览。

三、保存并上传照片
提交 base64 数据到服务器需要服务器支持, 我跳过了。

基本结构

因为一般手机像素都很高, 所以把原始图片尺寸限制在 300 里;

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="format-detection" content="telphone=no, email=no">
    <meta name="renderer" content="webkit">
    <meta name="HandheldFriendly" content="true">
    <meta name="screen-orientation" content="portrait">
    <meta name="x5-orientation" content="portrait">
    <title></title>
    <style media="screen">
      * {
        margin: 0;
        padding: 0;
      }
      img {display: block;}
    </style>
  </head>

  <body>
    <div class='upload'>
      <input type="file" id='file' capture="camera" accept="image/*">
      <label id="upload1"></label>

      <p> 原始图片 </p>
      <img class="old" width="300">

      <p> 修改图片 </p>
      <img class="new">
    </div>

    <script type="text/javascript">
      window.onload = function () {var dom = document.getElementById('file');
        dom.addEventListener('change', function (e) {});
      }
    </script>
  </body>

</html>

想要上传图片, 就要用 type="file" 获得文件信息, 其中下面几个重要属性,(在 pc 浏览器打开文件要很久, 所以可以选择性屏蔽)

属性 描述
accept 表示可以选择的文件 MIME 类型,多个 MIME 类型用英文逗号分开
multiple 是否可以选择多个文件,多个文件时其 value 值为第一个文件的虚拟路径
capture 表示,可以捕获到系统默认的设备,比如:camera– 照相机;camcorder– 摄像机;microphone– 录音
<input type="file" id='file' capture="camera" accept="image/*">

通过监听 input 变化触发方法

window.onload = function () {var dom = document.getElementById('file');
  dom.addEventListener('change', function (e) {});
}

Exif.js

Exif.js 提供了 JavaScript 读取图像的原始数据的功能扩展,例如:拍照方向、相机设备型号、拍摄时间、ISO 感光度、GPS 地理位置 等数据。
EXIF 数据主要来自拍摄的照片,多用于移动端开发,PC 端也会用到,此插件兼容主流浏览器,IE10 以下不支持。

<script src="https://cdn.jsdelivr.net/npm/exif-js"></script>

为了不让用户等待太久, 我们可以在一系列操作之前先展示获取的图片, 可以直接从本地先读取出原始数据, 然后在页面上展示出来, 下面用到的知识点之前已经写过就不再重复了, 可以前往浏览文章
关于前端上传文件全面基础扫盲贴 (二) —– File
关于前端上传文件全面基础扫盲贴(四) —– FileReader

var file = e.target.files[0],
  // 旋转角度
  orientation = null,
  fReader = new FileReader();

// 限制大小格式
if (!filtration(file)) {return false;}

上面的 filtration(file)是我抽离出去的过滤函数

filtration: function(file, extend) {var extend = extend || {},
    settings = {
      size: extend.size || 5 * 1024 * 1024,
      reg: extend.reg || /image\/\w+/
    };

  if (file.size > settings.size || !settings.reg.test(file.type)) {alert(extend.msg || ('上传格式非图片类型或上传图片超过' + settings.size / 1024 / 1024 + 'M'))
    return false;
  }
  return true;
},

接下来要获取图片信息了, 解决部分手机会有旋转角度的问题的前提(可能链接格式问题, 生成文章之后没带链接, 只能自己复制粘贴访问地址了)
中文文档: http://code.ciaoca.com/javasc…
Demo: http://code.ciaoca.com/javasc…

EXIF.getData(file, function () {orientation = EXIF.getTag(this, 'Orientation');
  console.log('旋转角度:', orientation);
});

我们已经拿到原始数据,result属性中将包含一个 data: URL 格式的字符串以表示所读取文件的内容. 然后新建 img 对象加载资源

// 转码完成
fReader.onload = function (e) {
  // 加载图片
  var img = new Image();
  img.src = e.target.result;
  img.onload = function () {}
}

// 执行
fReader.readAsDataURL(file);

接着我们使用 canvas 画布重新设定尺寸重绘达到压缩的功能, 我只用到基础使用方法, 详情(Canvas API)

var canvas = document.createElement('canvas'),
  ctx = canvas.getContext('2d'),
  nSize = resetSize(this, 300, 300, 1, 600);

canvas.width = nSize.width;
canvas.height = nSize.height;
ctx.drawImage(this, 0, 0, nSize.width, nSize.height);

上面的 resetSize 是我自定义的一个方法, 可以设置高宽比例最大范围值, 可能有点繁琐, 因为我想控制的功能比较多, 中间容易发生冲突, 我暂时是后面设置能覆盖前面设置 (注意: 这里的 this 是上文的data: URL 格式)

resetSize: function(img, width, height, ratio, max) {
  var w = img.naturalWidth,
    h = img.naturalHeight,
    width = width || w,
    height = height || h;

  console.log('图片宽高:', w, h, '入参:', width, height, ratio, max);

  if (w != width) w = width;
  if (h != height) h = height;
  if (ratio && w / h != ratio) w = h * ratio;

  // 限制最大值范围
  if (max) {if (w > max && w >= h) {h = Math.ceil(h * max / w);
      w = max;
    } else if (h > max && h >= w) {w = Math.ceil(w * max / h);
      h = max;
    }
  }
  console.log('重设宽高:', w, h);
  return {width: w, height: h}
},

接下来就是比较复杂的图形操作了, 只要掌握了中心点位置就还好理解, 详情 (HTML 5 Canvas 参考手册)
上面拿到的 orientation 不是真的旋转角度, 只是给一个值代表拍摄方向,
最后返回新图片展示的 data URI, 这里水比较深, 我测试了 jpg 和 png 是没问题, 但是 gif 什么的就输出一个静态图, 详情(toDataURL)

var canvas = document.createElement('canvas'),
  ctx = canvas.getContext('2d'),
  nSize = picProcess.resetSize(this, 600, 400);

switch (+ orientation) {
  case 3:
    canvas.width = nSize.width;
    canvas.height = nSize.height;
    ctx.rotate(180 * Math.PI / 180);
    ctx.drawImage(this, -nSize.width, -nSize.height, nSize.width, nSize.height);
    break;
  case 6:
    canvas.width = nSize.height;
    canvas.height = nSize.width;
    ctx.rotate(90 * Math.PI / 180);
    ctx.drawImage(this, 0, -nSize.height, nSize.width, nSize.height);
    break;
  case 8:
    canvas.width = nSize.height;
    canvas.height = nSize.width;
    ctx.rotate(270 * Math.PI / 180);
    ctx.drawImage(this, -nSize.width, 0, nSize.width, nSize.height);
    break;
  default:
    canvas.width = nSize.width;
    canvas.height = nSize.height;
    ctx.drawImage(this, 0, 0, nSize.width, nSize.height);
    break;
}

return canvas.toDataURL(file.type, 0.8);

现在回看上面, 我们已经实现了获取图片, 重绘图片, 读取本地图片, 剩下就是预览了, 我简单实现一个支持 id 和 class 的方法, 也能判断出 img 和 div 智能设置展示方式

preview: function(selector, url) {var isId = /^#\w+/ig.test(selector),
    isClass = /^\.\w+/ig.test(selector),
    name = selector.slice(1),
    dom = null;

  // 选择器类型
  if (isId) {dom = document.getElementById(name);
  } else if (isClass) {dom = document.getElementsByClassName(name)[0];
  } else {alert('选择器传参不支持!');
    return false;
  }

  // 判断类型
  if (dom.nodeName == 'IMG') {dom.src = url;} else {dom.style.backgroundImage = 'url(' + url + ')';
  };
}

然后我们在监听方法里调用就好了. 为了更加直观的看到代码效果, 我简单写了一个 demo 版本, 可以直接下载本地运行, 不依赖任何环境实战 Demo

大家且看且珍惜吧, 我可能后续还会研究手势旋转处理之类更加复杂的东西, 但也说不好, 毕竟还是太懒散了.
循例还是得加上一句, 因为都没有很深入测试过, 所以肯定会有些隐藏的问题, 欢迎大家指出最好还有答案咯

正文完
 0