h5移动端手机拍照,压缩(旋转),上传 (vue,FileReader,exif-js,canvas,axios)

**最近做一个项目,手机端拍照,压缩图片,上传到服务器,下面将会写到用的技术和遇到的问题和解决办法 … **

现在我们用手机上传图片到网上提交数据,但是可能图片的文件比较大,像拍照拍出来的就有可能有2m,这个时候就需要把图片压缩进行处理。

一、图片上传前端压缩的意义

  1. 对于大尺寸图片的上传,在前端进行压缩可以省流量
  2. 上传图片尺寸较小,上传速度会比较快,交互会更加流畅,同时大大降低了网络异常导致上传失败风险。最大的意义提高了用户体验。

二、图片压缩(旋转),上传用到的技术

  1. 使用FileReader获取图片数据

    HTML5定义了FileReader作为文件API的重要成员用于读取文件,根据W3C的定义,FileReader接口提供了读取文件的方法和包含读取结果的事件模型。

    相关文章:
    https://blog.csdn.net/zk437092645/article/details/8745647
    https://www.cnblogs.com/hhhyaaon/p/5929492.html

  2. canvas

相关文章:
https://blog.csdn.net/xu_ya_fei/article/details/51568594
https://www.cnblogs.com/xingfuboke/p/5749170.html
http://www.w3school.com.cn/html5/canvas_drawimage.asp

  1. Exif.js ,Exif.js 提供了 JavaScript 读取图像的原始数据的功能扩展,例如:拍照方向、相机设备型号、拍摄时间、ISO 感光度、GPS 地理位置等数据。

相关文章:
http://code.ciaoca.com/javascript/exif-js/

  1. axios

三、遇到的问题

  1. 在ios手机上拍照压缩之后的图片,会出现逆时针旋转90度的bug,Android手机不存在这样的问题。

    解决办法:

(1). 获取到照片拍摄的方向角,对非横拍的IOS照片进行角度旋转修正。
(2). 用exif.js读取照片的拍摄信息,主要用到定位属性Orientation。
(3). exif.js提供的一张照片的Orientation属性如下:

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

这里写图片描述

四、bug的显示

点击文件选择图片就可以拍照,左侧是苹果手机上出现的问题,右侧是正常的效果图。

这里写图片描述

代码展示
github https://github.com/lian-fei/phoneScan

<template >
  <div class="root">
    <!-- 点击拍照 手机拍照,浏览器选择照片,只能选择一张-->
    <h2>选择图片</h2>
    <div>
      <input type="file" accept="image/*" capture="camera" @change="imageFileChange">
    </div>
    <br>
    <br>
    <br>
    <h3>压缩之前的图片</h3>
    <img :src="compressFrondImage" alt="">
    <h3>压缩之后的图片</h3>
    <img :src="compressEndImage" alt="">
  </div>
</template>

<script>
import EXIF from 'exif-js'
export default {
  name: '',
  data () {
    return {
      compressFrondImage: '', // 压缩之前的图片
      compressEndImage: '' // 压缩之后的图片
    }
  },
  components: {},
  created () {},
  mounted () {},
  methods: {
    /**
     * 图片文件发生变化
     */
    imageFileChange (e) {
      this.file = e.target.files[0]
      console.log(this.file)
      if (this.file) {
        // FileReader
        let reader = new FileReader()
        reader.readAsDataURL(this.file)
        reader.onload = (e) => {
          console.log(e.target)
          this.compressFrondImage = e.target.result
          this.compressImages(this.compressFrondImage)
        }
      }
    },
    /**
     * 压缩图片
     */
    compressImages (res) {
      let defaultImage = {
        width: 1440,
        height: 1080,
        quality: 0.8, // 压缩图片的质量
        orientation: '' // 获取照片方向角属性,用户旋转控制
      }
      var img = new Image()
      img.src = res
      let initSize = img.src.length
      img.onload = () => {
        // 方便手机测试
        alert('压缩之前宽度: ' + img.width)
        alert('压缩之前高度: ' + img.height)
        // 方便浏览器测试
        console.log('压缩之前宽度: ' + img.width)
        console.log('压缩之前高度: ' + img.height)
        var canvas = document.createElement('canvas')
        var ctx = canvas.getContext('2d')
        if (img.width > defaultImage.width) {
          img.height = img.height * (defaultImage.width / img.width)
          img.width = defaultImage.width
        }
        if (img.height > defaultImage.height) {
          img.width *= defaultImage.height / img.height
          img.height = defaultImage.height
        }
        canvas.width = img.width
        canvas.height = img.height
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        EXIF.getData(this.file, () => { // IMG_FILE为图像数据
          // 是否是iPhone手机,iPhone 拍照之后的压缩是逆时针旋转90,针对iphone做一下处理
          if (navigator.userAgent.match(/iphone/i)) {
            defaultImage.orientation = EXIF.getTag(this.file, 'Orientation')
            // translate是平移变换,scale(-1,1)是向左翻转,rotate是顺时针旋转。
            // defaultImage.orientation = 6   // 测试iPhone手机
            alert('Orientation:' + defaultImage.orientation) // 拍照方向
            switch (Number(defaultImage.orientation)) {
              case 2:
                ctx.translate(img.width, 0)
                ctx.scale(-1, 1)
                ctx.drawImage(img, 0, 0, img.width, img.height)
                break
              case 3:
                ctx.rotate(180 * Math.PI / 180)
                ctx.drawImage(img, -img.width, -img.height, img.width, img.height)
                break
              case 4:
                ctx.translate(img.width, 0)
                ctx.scale(-1, 1)
                ctx.rotate(180 * Math.PI / 180)
                ctx.drawImage(img, -img.width, -img.height, img.width, img.height)
                break
              case 5:
                ctx.translate(img.width, 0)
                ctx.scale(-1, 1)
                ctx.rotate(90 * Math.PI / 180)
                ctx.drawImage(img, 0, -img.width, img.height, img.width)
                break
              case 6:
                canvas.width = img.height
                canvas.height = img.width
                ctx.rotate(90 * Math.PI / 180)
                ctx.drawImage(img, 0, 0, img.width, -img.height)
                break
              case 7:
                ctx.translate(img.width, 0)
                ctx.scale(-1, 1)
                ctx.rotate(270 * Math.PI / 180)
                ctx.drawImage(img, -img.height, 0, img.height, img.width)
                break
              case 8:
                ctx.rotate(270 * Math.PI / 180)
                ctx.drawImage(img, -img.height, 0, img.height, img.width)
                break
              default:
                ctx.drawImage(img, 0, 0, img.width, img.height)
            }
          } else {
            ctx.drawImage(img, 0, 0, img.width, img.height)
          }
          var imgUrl = canvas.toDataURL('image/jpeg', defaultImage.quality)
          // 手机端测试
          alert('压缩率:' + ~~(100 * (initSize - imgUrl.length) / initSize) + '%')
          alert('压缩之后宽度: ' + img.width)
          alert('压缩之后高度: ' + img.height)
          // 浏览器测试
          console.log('压缩前:' + initSize)
          console.log('压缩后:' + imgUrl.length)
          console.log('压缩率:' + ~~(100 * (initSize - imgUrl.length) / initSize) + '%')
          console.log('压缩之后宽度: ' + img.width)
          console.log('压缩之后高度: ' + img.height)
          console.log('压缩之后base64地址')
          console.log(imgUrl)
          // 压缩之后的base64 图片地址
          this.compressEndImage = imgUrl
          // TODO 上传图片文件
          this.uploadImage()
        })
      }
    },
    /**
     * 上传图片
     */
    uploadImage: function () {
    },
    /**
     * 瓦片压缩 正在测试
     */
    imagesCompress: function (img) {
      // 用于压缩图片的canvas
      var canvas = document.createElement('canvas')
      var ctx = canvas.getContext('2d')
      // 瓦片canvas
      var tCanvas = document.createElement('canvas')
      var tctx = tCanvas.getContext('2d')

      var initSize = img.src.length
      var width = img.width
      var height = img.height

      // 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
      var ratio
      if ((ratio = width * height / 4000000)>1) {
        ratio = Math.sqrt(ratio)
        width /= ratio
        height /= ratio
      } else {
        ratio = 1
      }

      canvas.width = width
      canvas.height = height

      // 铺底色
      ctx.fillStyle = '#fff'
      ctx.fillRect(0, 0, canvas.width, canvas.height)

      // 如果图片像素大于100万则使用瓦片绘制
      var count
      if ((count = width * height / 1000000) > 1) {
        count = ~~(Math.sqrt(count)+1) // 计算要分成多少块瓦片

        // 计算每块瓦片的宽和高
        var nw = ~~(width / count)
        var nh = ~~(height / count)

        tCanvas.width = nw
        tCanvas.height = nh

        for (var i = 0; i < count; i++) {
          for (var j = 0; j < count; j++) {
            tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)

            ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
          }
        }
      } else {
        ctx.drawImage(img, 0, 0, width, height)
      }

      // 进行最小压缩
      var ndata = canvas.toDataURL('image/jpeg', 0.8)

      console.log('压缩前:' + initSize)
      console.log('压缩后:' + ndata.length)
      console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + '%')

      tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
      return ndata
    }
  }
}
</script>

<style lang="less">
.root{
  width:50%;
  margin: 0 auto;
}
.root span {
  font-size: 30px;
  font-weight: bold;
  color: rgb(245, 45, 144);
  cursor: pointer;
  &:hover {
    color: rgb(221, 70, 39);
  }
}
img{
  width:100%;
}
</style>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章