H5拍照上傳填坑彙總

前言

最近工做一直在使用vue+vux作移動端項目,有一個拍照上傳照片的需求,發現vux裏並無實現,調研過非官方的vux-uploader後,感受還不是很理想。javascript

其實網上已經能夠找到不少已經實現的成熟方案,可是在調研這個需求的時候,我發如今各類實現方案中也有一些puzzle的知識點,所以本身動手擼了一個輪子vux-uploader-component,並記錄一二。html

需求

組件的交互功能要求以下:前端

  1. html5調用手機相機
  2. 渲染圖片爲縮略圖
  3. 前端壓縮圖片
  4. 預覽大圖
  5. 刪除當前圖片
  6. 自動上傳

部分關鍵技術點的實現方案

使用html media capture調用手機端的相機

<input type="file" accept="image/*" capture />
複製代碼

既然是在HTML5規範中,那最關心的問題確定是兼容性了vue

html-media-capture-caniuse

html-media-capturehtml5

能夠看到,在大部分的主流平臺,兼容性還能夠接受,andriod2-4 都支持,只是在ios 6-10支持不太好。java

感興趣的能夠在本身的手機上測測兼容性ios

html-media-capture-demo

html-media-capture demogit

使用ULR.createObjectURL獲取圖片地址

blobURL = ULR.createObjectURL(object)
複製代碼

object參數能夠爲FileBlobMediaSourcegithub

在這一塊能夠衍生出好幾個問題:web

  • 已知獲取圖片地址的方法有URL.createObjectURLFileReader.readAsDataURL,那應該使用哪一個?爲何?
  • IOS中拍照獲取的圖片會自動旋轉,爲何?怎麼解決?
  • FileBlob是什麼關係?Blob URLData URL又有什麼區別?Data URL怎麼轉換成Blob?

使用canvas來壓縮圖片

能夠從兩個方面能夠進行壓縮:

  • 取一個最大寬度的限制對圖片的寬高尺寸進行等比例的縮小

    canvas.width = Math.min(image.naturalWidth, option.maxWidth)
    const ratio = canvas.width / image.naturalWidth
    canvas.height = image.naturalHeight * ratio
    複製代碼
  • canvas.toDataURL指定生成jpeg或者webp格式的圖片,能夠指定0-1之間的encoderOptions

    dataURL = canvas.toDataURL("image/jpeg", encoderOptions)
    複製代碼

最後生成的圖片大小 = 原圖大小 * ratio * encoderOption

使用FormData來上傳

const formData = new FormData()
formData.append('file', blob)
複製代碼

這是XHR Level2的產物,能夠方便的以鍵值對的形式插入。

最大的優點是能夠經過XMLHttpRequest.send()來異步提交二進制文件。

後期還能夠經過Blobslice來擴展分片上傳功能。

知識點剖析

FileReader和URL.createObjectURL的區別

關於FileReaderURL.createObjectURL的用法就不詳細介紹了,感興趣的自行google。

咱們如今只須要知道

  • 經過FileReader.readAsDataURL(file)能夠獲取一段data:base64的字符串

  • 經過URL.createObjectURL(blob)能夠獲取當前文件的一個內存URL

既然這兩個API均可以知足咱們獲取圖片地址的需求,那它們之間的區別在哪呢?

一、執行時機

  • createObjectURL是同步執行(當即的)
  • FileReader.readAsDataURL是異步執行(過一段時間)

二、內存使用

  • createObjectURL返回一段帶hashurl,而且一直存儲在內存中,直到document觸發了unload事件(例如:document close)或者執行revokeObjectURL來釋放。
  • FileReader.readAsDataURL則返回包含不少字符的base64,並會比blob url消耗更多內存,可是在不用的時候會自動從內存中清除(經過垃圾回收機制)

三、兼容性

  • createObjectURL支持從IE10往上的全部現代瀏覽器
  • FileReader.readAsDataURL一樣支持從IE10往上的全部現代瀏覽器

從上面答案不難看出,二者的優劣勢

  • 使用createObjectURL能夠節省性能並更快速,只不過須要在不使用的狀況下手動釋放內存
  • 若是不太在乎設備性能問題,並想獲取圖片的base64,則推薦使用FileReader.readAsDataURL

參考

相機拍照的圖片會旋轉

從上面的createObjectURL獲取到圖片的地址後,咱們能夠插入到頁面元素的background-image屬性展現這個圖片,PC端模擬器展現沒有問題,但手機真機拍照獲得的圖片會有逆時針的90°旋轉。

爲何從相機拍照獲取的圖片會旋轉呢?

是由於從相機拍照獲取的圖片的EXIF(Exchangeable image file format)會默認設置一個orientation tag

目前只有jpeg格式的圖片會有

orientation

:point_up_2:上圖就是orientation tag與圖片旋轉角度的對應關係

如何解決這個問題呢?

一、獲取圖片的orientation

  • 須要考慮兼容的話,建議使用Exif.js
  • 若是不但願有外部依賴,並且對兼容性要求不是那麼高的話,能夠利用DataView來獲取,詳情見stackoverflow高贊回答

二、根據圖片的orientation作對應的旋轉

switch (orientation) {
   case 2:
     // horizontal flip
     ctx.translate(width, 0);
     ctx.scale(-1, 1);
     break;
   case 3:
     // 180 rotate left
     ctx.translate(width, height);
     ctx.rotate(Math.PI);
     break;
   case 4:
     // vertical flip
     ctx.translate(0, height);
     ctx.scale(1, -1);
     break;
   case 5:
     // vertical flip + 90 rotate right
     ctx.rotate(0.5 * Math.PI);
     ctx.scale(1, -1);
     break;
   case 6:
     // 90 rotate right
     ctx.rotate(0.5 * Math.PI);
     ctx.translate(0, -height);
     break;
   case 7:
     // horizontal flip + 90 rotate right
     ctx.rotate(0.5 * Math.PI);
     ctx.translate(width, -height);
     ctx.scale(-1, 1);
     break;
   case 8:
     // 90 rotate left
     ctx.rotate(-0.5 * Math.PI);
     ctx.translate(-width, 0);
     break;
複製代碼

參考

File和Blob的關係?Blob Url和DataURL的區別?DataURL如何轉成Blob?

File和Blob的關係

input onchange中返回的圖片對象其實就是一個File對象。

Blob對象是一個用來包裝二進制文件的容器,File繼承於Blob

FileReader是用來讀取內存中的文件的API,支持FileBlob兩種格式。

Blob Url和Data URLs的區別

Blob Url只能在瀏覽器中經過URL.createObjectURL(blob)建立,當不使用的時候,須要URL.revokeObjectURL(blobURL)來進行釋放。

能夠簡單理解爲對應瀏覽器內存文件中的軟連接。該連接只能存在於瀏覽器單一實例或對應會話中(例如:頁面的生命週期)

blobURL = URL.createObjectURL(blob)

// blob:http://localhost:8000/xxxxxxxx
複製代碼

Data URLs能夠獲取文件的base64

data:[<mediatype>][;base64],<data>
複製代碼

mediatype是個 MIME 類型的字符串,例如 "image/jpeg" 表示 JPEG 圖像文件。若是被省略,則默認值爲 text/plain;charset=US-ASCII

能夠經過FileReader.readAsDataURL獲取

const reader = new FileReader();
reader.addEventListener("load", e => {
    const dataURL = e.target.result;
})
reader.readAsDataURL(blob);
複製代碼

DataURL如何轉成Blob?

function dataURItoBlob(dataURI) {
  // convert base64 to raw binary data held in a string
  // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
  var byteString = atob(dataURI.split(',')[1]);

  // separate out the mime component
  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

  // write the bytes of the string to an ArrayBuffer
  var ab = new ArrayBuffer(byteString.length);

  // create a view into the buffer
  var ia = new Uint8Array(ab);

  // set the bytes of the buffer to the correct values
  for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
  }

  // write the ArrayBuffer to a blob, and you're done
  var blob = new Blob([ab], {type: mimeString});
  return blob;

}
複製代碼

參考

上傳進度條從一開始上傳就是100%,爲何?

衆所周知,目前監聽上傳文件進度的主流方式是使用XHR的onprogress事件來實現,可是爲何在我本地調試上傳的時候,onprogress只被調用了一次呢?

在XHR2中有一個事件對象ProgressEvent,如下幾種監聽事件均可以獲取到這個對象:

事件名稱 觸發時機
loadstart 請求發起
progress 傳遞數據
abort 請求被停止(例如,經過abort()方法來觸發)
error 請求失敗
load 請求成功完成後
timeout 在指定時間內,請求超時時觸發
loadend 請求完成後(不論請求成功仍是失敗)

ProgressEvent的事件循環以下:

  1. 每一個請求發起後先觸發loadstart,請求完成的flagfalse

  2. 在請求完成的flag設置爲true以前,以50ms的間隔來輪詢觸發progress事件

  3. 當請求完成時,請求完成的flagtrue,根據請求完成的結果狀態,觸發abort,error,load,timeout其中之一。

  4. 請求完成後觸發loadend

至此,咱們就很清楚的知道了,爲何就算咱們在本地上傳,並在progress的回調裏console.log也只執行一次的緣由了:本地上傳請求事件小於50ms

只要將network調成slow 3G,並換一張高清像素的大圖片進行上傳,就能夠看到progress事件會在上傳完成以前以50ms的間隔調用。

參考

最後

小小推廣一波基於weui風格實現的移動端vue圖片上傳組件vux-uploader-component

vux-uploader-component

歡迎掃碼體驗,Star, Issue, PR:)

相關文章
相關標籤/搜索