最近工做一直在使用vue+vux作移動端項目,有一個拍照上傳照片的需求,發現vux裏並無實現,調研過非官方的vux-uploader後,感受還不是很理想。javascript
其實網上已經能夠找到不少已經實現的成熟方案,可是在調研這個需求的時候,我發如今各類實現方案中也有一些puzzle的知識點,所以本身動手擼了一個輪子vux-uploader-component,並記錄一二。html
組件的交互功能要求以下:前端
<input type="file" accept="image/*" capture />
複製代碼
既然是在HTML5規範中,那最關心的問題確定是兼容性了vue
html-media-capturehtml5
能夠看到,在大部分的主流平臺,兼容性還能夠接受,andriod
2-4 都支持,只是在ios
6-10支持不太好。java
感興趣的能夠在本身的手機上測測兼容性ios
ULR.createObjectURL
獲取圖片地址blobURL = ULR.createObjectURL(object)
複製代碼
object參數能夠爲
File
、Blob
、MediaSource
github
在這一塊能夠衍生出好幾個問題:web
URL.createObjectURL
和FileReader.readAsDataURL
,那應該使用哪一個?爲何?IOS
中拍照獲取的圖片會自動旋轉,爲何?怎麼解決?File
和Blob
是什麼關係?Blob URL
和Data 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()
來異步提交二進制文件。
後期還能夠經過Blob
的slice
來擴展分片上傳功能。
關於FileReader
和URL.createObjectURL
的用法就不詳細介紹了,感興趣的自行google。
咱們如今只須要知道
經過FileReader.readAsDataURL(file)
能夠獲取一段data:base64
的字符串
經過URL.createObjectURL(blob)
能夠獲取當前文件的一個內存URL
既然這兩個API均可以知足咱們獲取圖片地址的需求,那它們之間的區別在哪呢?
一、執行時機
createObjectURL
是同步執行(當即的)FileReader.readAsDataURL
是異步執行(過一段時間)二、內存使用
createObjectURL
返回一段帶hash
的url
,而且一直存儲在內存中,直到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
:point_up_2:上圖就是orientation tag
與圖片旋轉角度的對應關係
如何解決這個問題呢?
一、獲取圖片的orientation
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的關係
從input onchange
中返回的圖片對象其實就是一個File
對象。
而Blob
對象是一個用來包裝二進制文件的容器,File
繼承於Blob
。
FileReader
是用來讀取內存中的文件的API,支持File
和Blob
兩種格式。
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;
}
複製代碼
參考
衆所周知,目前監聽上傳文件進度的主流方式是使用XHR的onprogress
事件來實現,可是爲何在我本地調試上傳的時候,onprogress
只被調用了一次呢?
在XHR2中有一個事件對象ProgressEvent
,如下幾種監聽事件均可以獲取到這個對象:
事件名稱 | 觸發時機 |
---|---|
loadstart | 請求發起 |
progress | 傳遞數據 |
abort | 請求被停止(例如,經過abort() 方法來觸發) |
error | 請求失敗 |
load | 請求成功完成後 |
timeout | 在指定時間內,請求超時時觸發 |
loadend | 請求完成後(不論請求成功仍是失敗) |
ProgressEvent
的事件循環以下:
每一個請求發起後先觸發loadstart
,請求完成的flag
爲false
在請求完成的flag
設置爲true
以前,以50ms的間隔來輪詢觸發progress
事件
當請求完成時,請求完成的flag
爲true
,根據請求完成的結果狀態,觸發abort
,error
,load
,timeout
其中之一。
請求完成後觸發loadend
至此,咱們就很清楚的知道了,爲何就算咱們在本地上傳,並在progress
的回調裏console.log
也只執行一次的緣由了:本地上傳請求事件小於50ms
只要將network調成slow 3G,並換一張高清像素的大圖片進行上傳,就能夠看到progress
事件會在上傳完成以前以50ms的間隔調用。
參考
小小推廣一波基於weui風格實現的移動端vue圖片上傳組件vux-uploader-component
歡迎掃碼體驗,Star, Issue, PR:)