背景
移動(dòng)端工單報(bào)修的場景中稻薇,上傳圖片的功能已經(jīng)屢見不鮮嫂冻,但現(xiàn)在手機(jī)像素普遍較高,隨手拍一張圖片都6塞椎、7M桨仿,十幾兆的圖片也并不罕見。如果這些未處理的圖片直接隨數(shù)據(jù)上傳向服務(wù)器案狠,不但會(huì)占用更多的存儲(chǔ)空間服傍,而且用戶也要等更久的時(shí)間,體驗(yàn)性會(huì)差很多骂铁,同時(shí)更長的傳輸時(shí)間伴嗡,也加大了問題發(fā)生的概率,基于這些情況从铲,壓縮圖片并上傳的需求應(yīng)運(yùn)而生。
基本原理
- 應(yīng)用FileReader 將文件轉(zhuǎn)換為URL格式的字符串澄暮,這個(gè)URL可以直接被Image 節(jié)點(diǎn)使用
- Imgage使用上面的URL生成image節(jié)點(diǎn)名段,此時(shí)可以獲取原始圖片的width、height等相關(guān)信息
- 應(yīng)用canvas重新繪制壓縮后的圖片
- 將壓縮后的canvas上的圖片轉(zhuǎn)換成Blob泣懊,并上傳到服務(wù)器
FileReader
FileReader
對象允許Web應(yīng)用程序異步讀取存儲(chǔ)在用戶計(jì)算機(jī)上的文件(或原始數(shù)據(jù)緩沖區(qū))的內(nèi)容伸辟,使用File
或Blob
對象指定要讀取的文件或數(shù)據(jù)。
其中File對象可以是來自用戶在一個(gè)<input>
元素上選擇文件后返回的FileList
對象,也可以來自拖放操作生成的DataTransfer
對象,還可以是來自在一個(gè)HTMLCanvasElement
上執(zhí)行mozGetAsFile()
方法后返回結(jié)果馍刮。
FileReader.readAsDataURL()
該方法會(huì)讀取指定的 Blob 或 File 對象信夫。讀取操作完成的時(shí)候,readyState 會(huì)變成已完成(DONE),并觸發(fā) loadend 事件静稻,同時(shí) result 屬性將包含一個(gè)data:URL格式的字符串(base64編碼)以表示所讀取文件的內(nèi)容警没。
canvas
canvas
用來轉(zhuǎn)換圖片,將原來的圖片“另存為”新圖片振湾。這里核心方法是cavas的drawImage
方法來壓縮圖片杀迹。
cavasObj.drawImage(img, x, y, width, height)
其中參數(shù)img 為要處理的源圖片,x為canvas生成圖片的起始x位置押搪,y為圖片的起始y位置树酪,width為生成圖片最終的寬度,heigth為圖片最終高度大州。當(dāng)width(height)小于源圖片的寬度(高度)時(shí)续语,圖片就被壓縮了。
圖片上傳
上傳圖片至服務(wù)器厦画,使用formData
對象生成表單疮茄,這里使用axios提交數(shù)據(jù)
function uploadFile (fileObj) {
const payload = new FormData()
payload.append("file", fileObj)
axios.post("upload", payload, {
headers: {"Content-Type": "multipart/form-data"},
params: {'userName': user, 'desc': 'desc‘, 'fileName': fileName}
})
}
完整示例代碼
// max 500 * 320
const maxHeight = 500
const maxWidth = 300
let img = new Image()
// fileObj為上傳的文件
img.src = fileObj.content
img.onload = () => {
const originHeight = img.height
const originWidth = img.width
let compressedWidth = img.height
let compressedHeight = img.width
if ((originWidth > maxWidth) && (originHeight > maxHeight)) {
// 更寬更高,
if ((originHeight / originWidth) > (maxHeight / maxWidth)) {
// 更加嚴(yán)重的高窄型苛白,確定最大高娃豹,壓縮寬度
compressedHeight = maxHeight
compressedWidth = maxHeight * (originWidth / originHeight)
} else {
//更加嚴(yán)重的矮寬型, 確定最大寬,壓縮高度
compressedWidth = maxWidth
compressedHeight = maxWidth * (originHeight / originWidth)
}
} else if (originWidth > maxWidth && originHeight <= maxHeight) {
// 更寬购裙,但比較矮懂版,以maxWidth作為基準(zhǔn)
compressedWidth = maxWidth
compressedHeight = maxWidth * (originHeight / originWidth)
} else if (originWidth <= maxWidth && originHeight > maxHeight) {
// 比較窄,但很高躏率,取maxHight為基準(zhǔn)
compressedHeight = maxHeight
compressedWidth = maxHeight * (originWidth / originHeight)
} else {
// 符合寬高限制躯畴,不做壓縮
}
let compressedCanvas = document.createElement("canvas")
let context = compressedCanvas.getContext("2d")
compressedCanvas.height = compressedHeight
compressedCanvas.width = compressedWidth
context.clearRect(0, 0, compressedWidth, compressedHeight)
context.drawImage(img, 0, 0, compressedWidth, compressedHeight)
let base64_img = compressedCanvas.toDataURL('image/jpeg')
const blobBin = atob(base64_img.split(',')[1]);
let data = [];
for(var i = 0; i < blobBin.length; i++) {
data.push(blobBin.charCodeAt(i));
}
let file = new Blob([new Uint8Array(data)], {type: 'image/png'});
// 上傳file 至服務(wù)器
uploadFile(file)
}