前幾天讀到掘金大神一篇有深度的文章 爲何視頻網站的視頻連接地址是blob?javascript
讓kk對以前在業務上使用過的blob對象有了深刻的瞭解。因此有感而發,也想要整理一下在以前的場景下,如何運用blob對象對圖片進行轉碼、上傳等操做。html
kk查詢了 MDN 對blob的定義:前端
Blob 對象表示一個不可變、原始數據的類文件對象。Blob 表示的不必定是JavaScript原生格式的數據。File 接口基於Blob,繼承了 blob 的功能並將其擴展使其支持用戶系統上的文件。java
基於此定義的業務思考,kk可聯想到 File對象 就是日常在form表單post形式上傳圖片文件的所對應的的形式。既然File基於blob,那麼post方法上傳一個blob對象也是可行的。web
如上圖爲身份證識別+視頻活體檢測在H5端實現的一個需求。在用戶拍照上傳身份證時候,前端要對該圖片進行壓縮、轉碼並上傳,與此同時保證上傳的性能和後端OCR算法的識別精度。算法
這裏順便想說起下:這個項目在剛開始的時候,上傳的性能比較差,緣由是上傳的圖片爲原圖,容量很大致使上傳時間太長,影響了用戶體驗。canvas
你們也知道,在微信裏發送原圖也是須要必定時間和資源的,在H5端也不例外。後端
通過反覆試驗,iOS的手機(kk試驗的是iPhone 7 Plus 後置1200萬像素),上傳的原圖容量爲3M,換在最新發布的安卓手機上,狀況就變得更加惡劣。設計師那臺最新發布的榮耀V20,2000萬像素,發送一張原圖高達8M,這就尷尬了。這勢必要在H5端上傳以前對圖片進行壓縮。瀏覽器
要用到blob的地方就在於壓縮事後,圖片轉碼並上傳的過程。事不宜遲,上代碼。服務器
功能實現主要分爲以下四大塊
preview(event) {
var _this = this;
const eventPath = event.path || (event.composedPath && event.composedPath());
// 當選取一張圖片時,新版iOS Safari瀏覽器會將圖片存儲在composedPath裏,或者從composedPath方法獲取,而普通瀏覽器依然會存放於path裏
var domId = eventPath[0].id;
var cardType = _this.cardTypeJudge(eventPath[0].id);
let files = document.getElementById(eventPath[0].id).files[0];
if (files.size / 1048576 > 15) {
_this.confirmShow = true;
_this.confirmReason = '圖片容量過大,請從新拍攝';
document.getElementById(domId).value = null
return false;
}
_this.smImage(files).then(resone => {
//上傳圖片
_this.uploadFile(resone, cardType).then(restwo => {
// console.log('restwo:' + restwo);
document.getElementById(eventPath[0].id).value = null;
_this.handleIdCardUpdateSuccess(_this.app.idCardFrontSrc, _this.app.idCardBackSrc, 1, cardType);
_this.submitBtnStatus();
});
});
},
複製代碼
在用戶從系統相冊選定圖片後,H5須要獲取觸發事件元素冒泡過程的全部元素,即點擊選擇的圖片地址。
通過kk分析,在Chrome中能夠經過event.path獲取,Firefox和Safari中發現event並無path屬性,確實須要調用event.composedPath()方法或者從event.composedPath取得。
這樣才能保證H5在各移動端瀏覽器的功能兼容性。
Event.composedPath() 是瀏覽器一個新的標準,沒想到移動端的更新步伐那麼快。
以下是kk參考其餘做者整理的獲取圖片元素地址的方法:
element.onClick(event) {
const ev = window.event || event;
const path = event.path || (event.composedPath && event.composedPath());
console.log(path) //[button#btn, div, body, html, document, Window]
}
複製代碼
smImage(files) {
var _this = this;
return new Promise((resolve, reject) => {
// 壓縮圖片須要的一些元素和對象
var reader = new FileReader(),
//建立一個img對象
img = new Image();
// 縮放圖片須要的canvas
var canvas = document.getElementById("imgcanvas");;
var context = canvas.getContext('2d');
// base64地址圖片加載完畢後
img.onload = function () {
// 圖片原始尺寸
var originWidth = this.width;
var originHeight = this.height;
// 最大尺寸限制,可經過國設置寬高來實現圖片壓縮程度
var maxWidth = 1300,
maxHeight = 1300;
// 目標尺寸
var targetWidth = originWidth,
targetHeight = originHeight;
// 圖片尺寸超過1300x1300的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更寬,按照寬度限定尺寸
targetWidth = maxWidth;
targetHeight = Math.round(maxWidth * (originHeight / originWidth));
} else {
targetHeight = maxHeight;
targetWidth = Math.round(maxHeight * (originWidth / originHeight));
}
}
// canvas對圖片進行縮放
canvas.width = targetWidth;
canvas.height = targetHeight;
// 清除畫布
context.clearRect(0, 0, targetWidth, targetHeight);
// 圖片壓縮
context.drawImage(img, 0, 0, targetWidth, targetHeight);
/*第一個參數是建立的img對象;第二個參數是左上角座標,後面兩個是畫布區域寬高*/
//壓縮後的圖片base64 url
/*canvas.toDataURL(mimeType, qualityArgument),mimeType 默認值是'image/jpeg'; * qualityArgument表示導出的圖片質量,只要導出爲jpg和webp格式的時候此參數纔有效果,默認值是0.92*/
var newUrl = canvas.toDataURL('image/jpeg', 0.5); //base64 格式
var blob = _this.convertBase64UrlToBlob(newUrl);
// console.log(blob);
// console.log('canvas寬度:' + targetWidth + ' 長度:' + targetHeight);
if (targetWidth < targetHeight) {
_this.isLongImg = true;
} else {
_this.isLongImg = false;
}
resolve(blob);
};
// 文件base64化,以便獲知圖片原始尺寸
img.src = _this.app.getFilePath(files);
})
}
複製代碼
圖片壓縮整體採用canvas重繪的方法,對圖片的尺寸和分辨率兩方面壓縮,其閾值經過canvas的各類API可實現人工調整。
以上所述,重繪大體分爲兩步:尺寸限定和壓縮繪製。
先建立一個canvas畫布,並限制其畫布寬高。kk將畫布寬maxWidth高maxHeight閾值都限定在了1300px,同時取得圖片的原始寬originWidth高originHeight進行比較。
當width和height縮減到同比例下低於寬高限定閾值時,則根據畫布寬高drawImage重繪。
主要取決於canvas.toDataURL() 方法中。當圖片重繪至畫布後,經過此方法可輸出一個Data URI,即默認爲png的base64串。
壓縮的比例,圖片輸出的分辨率就取決於這個方法的第二個參數:encoderOptions 。
調整這個參數就能夠導出不一樣分辨率的base64串,kk在這裏調整的是0.5,即50%分辨率的壓縮。
上面kk也說起過,使用post方法上傳文件,會取得一個File對象(基於blob),並做爲二進制流(binary)上傳到服務端。
如今重繪的圖片呈現的形式是base64,則須要一個base64轉blob的方法。
convertBase64UrlToBlob(urlData) {
var arr = urlData.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: mime
});
}
複製代碼
這個方法目前是怎麼進行轉碼的kk還沒弄清楚,只是做爲Util工具類一直運用在該項目裏,後期弄懂後會更新。
在此功能流程中,可調整的閾值有:
閾值調整後,性能相比於原來提高了50%,同時也保證了文字識別OCR的精度(實際上算法精度對圖片分辨率的要求並不須要原圖那麼高,因此進行適當的壓縮並不影響)。
性能提高50%體如今兩方面:功能提高和體驗提高。
原圖容量由8M及以上壓縮至2M如下,iOS原圖更是壓縮至1M如下,容量壓縮了70%左右。
圖片上傳速度由原來的8.0s左右,提高至如今的2.0s~2.6s(3s之內,也取決於當時的網絡情況和服務器的接口響應)。
這是我對這個項目開發經驗的整理,以及blob對象運用的一些我的理解。望指正!