在前端頁面,我們通常會遇到需要用戶上傳圖片的操作,可能還會在前端進行圖片編輯的操作(比如頭像的選區裁剪),然後如果圖片過大的話,我們還會對圖片進行壓縮。這些功能我們通常通過Canvas來進行,最後使用Canvas API函數toDataURL來得到圖片的Base64字符串,然後當我們要上傳到後臺的時候,會面臨2種選擇:
- 直接將圖片的Base64字符串Post到後端進行處理和保存
- 在前端將Base64字符串轉換成二進制的Blob對象形式,再使用常規的文件上傳形式(即FormData)來將其上傳到後端
第一種方式對前端來說比較簡單,主要的處理邏輯在後端。而第二種的話前端的工作就稍微複雜一些。考慮到後端採用接收二進制文件的方式來處理文件上傳的情況比較多,所以我們來看一下前面所說的第二種情況在前端怎麼來實現,以下是主要的示例代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用FormData上傳壓縮裁剪後的圖片Blob對象</title>
</head>
<body>
<input type="file" name="myfile" id="myfile" onchange="uploadHandler(event)">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
function uploadHandler(e) {
var files = e.target.files || e.dataTransfer.files;
if (files && files.length > 0) {
var file = files[0];
resizeImage(file).then(function (result) {
return typeof result === 'string' ? convertToBlob(result, file.type) : result;
}).then(function (blob) {
// 構建FormData
var formData = new FormData();
//注意:此處第3個參數最好傳入一個帶後綴名的文件名,否則很有可能被後臺認爲不是有效的圖片文件
formData.append("file", blob, file.name);
// 上傳文件
$.ajax({
url: '/api/upload',
method: "POST",
data: formData,
cache: false,
processData: false,
contentType: false
}).then(function (res) {
console.log(res);
}).catch(function (err) {
console.log(err);
})
});
}
}
/**
* 壓縮裁剪圖片
*/
function resizeImage(file) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function () {
var img = new Image();
img.onload = function () {
var w = this.naturalWidth;
var h = this.naturalHeight;
var maxW = 500;
var maxH = 500;
// 如果圖片尺寸小於最大限制,則不壓縮直接上傳
if (w <= maxW && h <= maxH) {
resolve(file);
return;
}
var level = 0.6;
var multiple = Math.max(w / maxW, h / maxH);
var resizeW = w / multiple;
var resizeH = h / multiple;
var canvas = document.createElement("canvas");
canvas.width = resizeW;
canvas.height = resizeH;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, resizeW, resizeH);
var base64Img = canvas.toDataURL(file.type, level);
var arr = base64Img.split(",");
resolve(arr[1]);
};
img.src = this.result;
};
reader.readAsDataURL(file);
});
}
/**
* 將圖片的base64字符串轉換爲Blob對象
*/
function convertToBlob(base64Str, fileType) {
var base64 = window.atob(base64Str);
var len = base64.length;
var buff = new ArrayBuffer(len);
var uarr = new Uint8Array(buff);
for (var i = 0; i < len; i++) {
uarr[i] = base64.charCodeAt(i);
}
var blob = null;
try {
blob = new Blob([buff], { type: fileType });
} catch (e) {
var BlobBuilder = window.BlobBuilder = (
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder
);
if (e.name === "TypeError" && BlobBuilder) {
var builder = new BlobBuilder();
builder.append(buff);
blob = builder.getBlob(fileType);
}
}
return blob;
}
</script>
</body>
</html>
代碼中值得注意的一點是下面這行代碼:
formData.append("file", blob, file.name);
如果不傳第三個參數的話,生成的表單數據中,上傳文件對應的filename會被設置爲blob
:
通常情況下這也是沒問題的。但是可能由於後端使用的不同框架或自己的邏輯代碼的原因,對上傳的文件名做了強制的後綴名檢查,會發生報錯導致上傳失敗,遇到這種情況,請記得使用上面的方式加上第三個參數,這樣問題應該就能迎刃而解了。