文件上傳那些事兒

最近M端項目中涉及到圖片上傳功能,現把項目中遇到的一些問題及解決辦法分享如下,與各位共同探討:

一、相關需求:

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()發送二進制流,參考此文,測了一部分常見機型,可以實現,具體是否可以在項目中使用還有待論證。二進制上傳實現部分代碼僅供參考:

01 var uploadFile = function(fileid, file) {
02   var xhr = new XMLHttpRequest(),
03       fd = new FormData,
04       url = "/picture/upload",
05       // 上傳進度
06       uploadProgress = function(evt) {
07         if (evt.lengthComputable) {
08           var percent = (evt.loaded * 100 / evt.total).toFixed(1);
09           // show percent
10         }else {
11           console.log('unable to compute');
12         }
13       },
14       uploadFailed = function(evt) {
15         // 上傳失敗
16       },
17       uploadCanceled = function(evt) {
18         // 取消上傳或網絡連接斷開!
19       };
20  
21   xhr.upload.addEventListener("progress", uploadProgress, false);
22   xhr.addEventListener("error", uploadFailed, false);
23   xhr.addEventListener("abort", uploadCanceled, false);
24   xhr.open("POST", url , true);
25   xhr.onreadystatechange = function(e) {
26     if (xhr.readyState == 4) {
27       if (xhr.status === 200) {
28         var data = xhr.response;
29         if (data != 0) {
30           // 上傳失敗
31         else {
32           // 上傳成功
33         }
34       else {
35         // 上傳失敗
36       }
37     }
38   };
39  
40   fd.append(fileid, file);
41   xhr.send(fd);
42 };

實現二: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-bug
最後採用的騰訊的一款壓縮方案,解決了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: -999em;}來隱藏。

#20150923 update start
使用label標籤for屬性觸發input[type=file]需要注意的一些問題:

1. label的for指向的是input[text/radio/checkbox/date]等元素trigger的是focus事件,而指向input[file]元素trigger的是click 事件,因此可以打開一個瀏覽器窗口,這就是我們使用label的for指向input[file],使用zepto的tap事件綁定時,不會觸發的原因。
2. 給input[type=file]設置display: none or visibility: hidden將不會工作,因爲表單提交時input的值不會被髮送到服務端。除上面解決的top:-999em;也可以使用以下CSS來設置隱藏:

1 input[type=file] {
2     width0.1px;
3     height0.1px;
4     opacity: 0;
5     overflowhidden;
6     positionabsolute;
7     z-index-1;
8 }

你可能會有疑問,這裏爲什麼不把寬高設置爲0而爲0.1px。設置爲0在一些瀏覽器tab頁可能不會被解析。同時設置position:absolute屬性來使其脫離標準流,不會影響其他元素的佈局。

如是想,使用label標籤的for屬性,我們就可以使用CSS3各種屬性來DIY按鈕各種樣式及效果了,不是嗎?

同時可以設置當input[file]focus時,label的outline樣式:

1 .input[type=file]:focus + label {
2     outline1px dotted #000;
3     outline: -webkit-focus-ring-color auto 5px;
4 }

Firefox中對:hover,:active支持良好,當時會忽略input[type=”file”]:focus設置,但是Firefox對input[file]支持focus、blur事件,我們可以通過JS來增加和刪除類has-focus來實現:

1 input.addEventListener( 'focus'function(){ input.classList.add( 'has-focus' ); });
2 input.addEventListener( 'blur'function(){ input.classList.remove( 'has-focus' ); });
3 .inputfile:focus + label,
4 .inputfile.has-focus + label {
5     outline: 1px dotted #000;
6     outline: -webkit-focus-ring-color auto 5px;
7 }

3. input[file]多選(multiple),現在多選在移動端和PC端支持都不太好,PC端IE9及以下瀏覽器不支持該屬性;移動端andriod系統某些自帶瀏覽器、UC瀏覽器支持情況各異。
#20150923 update end

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",
2 "name":"fanmian","type":"","size":2273852}

正常結果如下:

1 {"webkitRelativePath":"","lastModified":1433304214000,"lastModifiedDate":"2015-06-03T04:03:34.000Z",
2 "name":"fanmian.png","type":"image/png","size":2273852}

 解決方式:放開/image/.test(file.type)過濾,在壓縮時,拋出錯誤過濾。

h) html5上傳文件,Firefox支持重複選擇同一文件,其它瀏覽器不支持
解決方式:每次選擇文件後給input[type=file]賦值空。

2015-09-09補充
input[type=file]控件比較特殊:
對於ios,已實現file,ios7版本之前,可以喚起照片集裏的圖片文件;ios7後,實現了拍照和錄像的功能。不過在7.0.3裏有bug,程序會閃退;

對於andriod,如果使用的是瀏覽器,file類型的文件選擇,會喚起瀏覽器實現的文件選擇,不過文件的選擇,不同的手機,具體實現不同,web無法控制。如果在android app裏使用webkit的方式,需要android的webkit實現私有api接口,才能實現file選擇上傳。

input[type=file]控件在M端瀏覽器支持情況(部分機型) by qianqian and xiaocui of my team —2015.12.12 add

編號 手機設備 測試瀏覽器 是否支持拍照 是否支持從相冊選擇
1 iphone4S 微信瀏覽器 支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
2 iphone5S 微信瀏覽器 支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
3 iphone6 微信瀏覽器 支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
4 iphone6 plus 微信瀏覽器 支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
5 小米手機2S 微信瀏覽器 不支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
6 小米手機M4 微信瀏覽器 不支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
7 三星S6 ED 微信瀏覽器 不支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
8 三星SM-G5308W 微信瀏覽器 不支持 不支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
9 華爲榮耀7 微信瀏覽器 不支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
10 華爲P7 微信瀏覽器 不支持 支持
QQ瀏覽器 不支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
11 華爲PE-TL10 微信瀏覽器 不支持 支持
QQ瀏覽器 支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 支持 支持
12 酷派8675-A 微信瀏覽器 不支持 支持
QQ瀏覽器 不支持 支持
UC瀏覽器 支持 支持
手機自帶瀏覽器 不支持 不支持

五、參考鏈接:

Html5 File Upload with Progress
移動端Web上傳圖片實踐
圖片壓縮成base64,採用二進流上傳
Styling & Customizing File Inputs the Smart Way

注:本文爲轉載文章,原文信息如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章