最近M端專案中涉及到圖片上傳功能,現把專案中遇到的一些問題及解決辦法分享如下,與各位共同探討:
PS:文章已同步至個人部落格
一、相關需求:
1. 客戶端上限10M
2. 伺服器端上限2M
3. 檔案過濾
4. 顯示上傳進度
5. 非同步上傳
6. 多檔案上傳
二、需求分析:
1. 實現2,可使用canvas在前端實現壓縮(base64);
2. 實1、3,可採用file.size及/image/.test(file.type)過濾;
3. 實現4,使用XHR2實現上傳,新增進度時間監控,xhr.upload.addEventListener(“progress”, uploadProgress, false);
4. 實現5,使用XHR2實現上傳;
5. 多檔案上傳,單檔案迴圈上傳即可,但是相容進度時,需單檔案各自監控;
三、需求實現:
實現一:二進位制方式上傳
需求1、3、4、5、6皆可實現,但是伺服器端上傳2M,使用canvas方式壓縮後生成的是base64,若使用此方式上傳,必須把base64轉換成二進位制流,GitHub上也有相關文章把base64轉換成二進位制流的,使用xhr.sendAsBinary()傳送二進位制流,參考此文,測了一部分常見機型,可以實現,具體是否可以在專案中使用還有待論證。二進位制上傳實現部分程式碼僅供參考:
var uploadFile = function(fileid, file) { var xhr = new XMLHttpRequest(), fd = new FormData, url = "/picture/upload", // 上傳進度 uploadProgress = function(evt) { if (evt.lengthComputable) { var percent = (evt.loaded * 100 / evt.total).toFixed(1); // show percent }else { console.log('unable to compute'); } }, uploadFailed = function(evt) { // 上傳失敗 }, uploadCanceled = function(evt) { // 取消上傳或網路連線斷開! }; xhr.upload.addEventListener("progress", uploadProgress, false); xhr.addEventListener("error", uploadFailed, false); xhr.addEventListener("abort", uploadCanceled, false); xhr.open("POST", url , true); xhr.onreadystatechange = function(e) { if (xhr.readyState == 4) { if (xhr.status === 200) { var data = xhr.response; if (data != 0) { // 上傳失敗 } else { // 上傳成功 } } else { // 上傳失敗 } } }; fd.append(fileid, file); xhr.send(fd); };
實現二:base64上傳
需求1、2、3、5、6皆可實現,實現此種方式即基本的Get上傳,但是無法實時監控上傳檔案進度,需求4無法實現。
實現三:二進位制+base64
即上述兩種方案的綜合。也可參考此文移動端Web上傳圖片實踐中的例項。
四、問題總結:
M端瀏覽器各異,支援情況各異,現總結如下:
a) 部分酷派機型瀏覽器(微信、UC、QQ、百度),中興自帶瀏覽器不支援input[type=file];
解決方式:放棄
b) Adroid機型,不同瀏覽器對input[type=file]支援不同,有的沒有相簿選項,有的沒有相機選項。主要表現為小米、酷派部分機型的微信自帶瀏覽器。
解決方式:input[type=file]新增accept=’image/*’屬性,可實現某些adriod機型不出現文件選項。
c) 上傳檔案時,出現圖片自動旋轉的問題
解決方式:實現開源外掛CanvasResize中exif.js來糾正,實現此外掛可解決壓縮、糾正圖片旋轉,但Adroid上UC瀏覽器中會出現下圖問題:(國外人寫的外掛哪會管國內瀏覽器死活!)
最後採用的騰訊的一款壓縮方案,解決了UC瀏覽器的問題。
d) 使用壓縮外掛時需注意,PNG圖片壓縮時往往會偏大,可把壓縮成image/jpeg格式;
var cvs = document.createElement(‘canvas’);
var ctx = cvs.getContext(“2d”).drawImage(source_img_obj, 0, 0);
var newImageData = cvs.toDataURL(‘image/jpeg’, quality/100);
f) 因瀏覽器對input[type=file]顯示風格各異,專案使用label的for指向input[type=file]的id,並設定input{display:none};在Adroid部分瀏覽器上點選無反應;
解決方式:設定input{position: absolute; top: -99em;}來隱藏。
g) 在部分Adroid支援input[type=file]的瀏覽器中,當使用/image/.test(file.type)時,選擇圖片檔案會返回false。使用JSON.stringify(file)分析後發現,是file物件中的name欄位中沒有包含字尾,同時type欄位為空,使用this.value獲取路徑中也沒有包含字尾。因此過濾出現問題。
如下結果:
1 {“webkitRelativePath”:””,”lastModified”:1433304214000,”lastModifiedDate”:”2015-06-03T04:03:34.000Z”,“name”:”fanmian”,”type”:””,”size”:2273852}
正常結果如下:
1 {“webkitRelativePath”:””,”lastModified”:1433304214000,”lastModifiedDate”:”2015-06-03T04:03:34.000Z”,“name”:”fanmian.png”,”type”:”image/png”,”size”:2273852}
解決方式:放開/image/.test(file.type)過濾,在壓縮時,丟擲錯誤過濾。
h) html5上傳檔案,Firefox支援重複選擇同一檔案,其它瀏覽器不支援
解決方式:每次選擇檔案後給input[type=file]賦值空。
文中不妥之處,歡迎批評指正!
五、參考連結:
Html5 File Upload with Progress
移動端Web上傳圖片實踐
圖片壓縮成base64,採用二進流上傳