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>


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