說清楚檔案上傳
dir:接收上傳內容的目錄
public:存放前端測試頁面的目錄
依賴:用到了express和formidable,分別用來搭建本地服務和提供/upload上傳介面
版本1:
對應HTML程式碼
<!——
action=“/upload”:指定後端上傳介面
enctype=“multipart/form-data”:將檔案統一轉成
二進位制
的形式上傳
method:以POST形式進行上傳
——>
<
form
action
=
“/upload”
enctype
=
“multipart/form-data”
method
=
“post”
>
<!—— name屬性是必須的,給後端用的 ——>
<!—— multiple屬性代表支援多選上傳 ——>
<
input
type
=
“file”
name
=
“fileInput”
multiple
>
<
br
>
<
input
type
=
“submit”
value
=
“上傳”
>
form
>
上面HTML程式碼對應介面
此時選擇檔案後再點選上傳按鈕,上傳成功,如下圖所示:
dir目錄
也會多出檔案,檔名字是後端可定義的,如下圖所示:
以上我們沒有用到任何JS程式碼,就實現了最簡單的檔案上傳。後端/upload介面的實現也很簡單,先不用關心,後面我會給出原始碼展示並加以說明。
版本2:
版本1
存在的問題:上傳成功後當前頁面“不見了”,我們希望的是上傳成功後在當前頁面也能拿到後端返回的資訊,實際開發中也往往如此。我們對
版本1
的HTML程式碼進行改進,實際上只是增加了iframe標籤,並把form的target屬性值指向iframe的name值即可。
對應HTML程式碼
<!—— target 屬性規定在何處開啟 action URL ——>
我們選擇檔案並點選上傳按鈕後如下圖所示,可見後端返回的內容已經能在當前頁面接收了:
版本3:
這個版本要做的工作是:
隱藏iframe框,我們頁面並不需要它
把iframe框中資料提取出來
簡單封裝下程式碼,假如頁面有N個上傳按鈕時方便使用
我們的想法是,點選上圖中的上傳按鈕直接彈出系統檔案選擇框,如下所示:
選中檔案後,點選開啟按鈕直接開始上傳操作。大致的操作思路如下圖:
其中步驟3,我們用到了oFile。click()方法,IE8及以下瀏覽器是不支援的,也就是使用者必須手動點選框才能進行檔案選擇操作,解決辦法是:一開始在按鈕上覆蓋一個透明的框即可,不過我並不打算進行相關相容方法的處理。上面思路換成程式碼實現如下:
let
fileUpload
=
(
function
()
{
function
createIframe
(
opt
)
{
let
oIframe
=
document
。
createElement
(
‘iframe’
);
oIframe
。
name
=
opt
。
iframeName
;
oIframe
。
style
。
display
=
“none”
;
// none
document
。
body
。
appendChild
(
oIframe
);
return
oIframe
;
}
function
createForm
(
opt
)
{
let
oForm
=
document
。
createElement
(
“form”
);
oForm
。
action
=
opt
。
action
;
oForm
。
target
=
opt
。
iframeName
;
oForm
。
enctype
=
“multipart/form-data”
;
oForm
。
method
=
“post”
;
oForm
。
style
。
display
=
“none”
;
// none
// 建立表單內的File框
let
oFile
=
createFile
(
opt
);
oForm
。
appendChild
(
oFile
);
document
。
body
。
appendChild
(
oForm
);
return
{
oForm
,
oFile
};
}
function
createFile
(
opt
)
{
let
oFile
=
document
。
createElement
(
“input”
);
oFile
。
type
=
“file”
;
oFile
。
name
=
“fileInput”
;
oFile
。
multiple
=
true
;
oFile
。
accept
=
opt
。
type
。
join
(
‘,’
);
return
oFile
;
}
function
handleUpload
(
opt
)
{
// 建立iframe
let
oIframe
=
createIframe
(
opt
);
// 建立表單
let
obj
=
createForm
(
opt
);
// 插入完畢後執行點選
obj
。
oFile
。
click
();
// 監聽change,並執行submit提交
obj
。
oFile
。
addEventListener
(
“change”
,
()
=>
obj
。
oForm
。
submit
());
oIframe
。
addEventListener
(
“load”
,
opt
。
endFn
);
oIframe
。
addEventListener
(
“load”
,
function
()
{
// 我們可不希望每點選一次按鈕頁面都多出上面建立的標籤,所以拿到資料後把建立的標籤刪除
oIframe
。
remove
();
obj
。
oForm
。
remove
();
});
}
function
init
(
opt
)
{
opt
=
opt
||
{};
// 可以設定下opt的預設值,做下容錯處理
handleUpload
(
opt
);
}
return
{
init
:
init
}
})();
let
oBtn1
=
document
。
querySelector
(
“#btn1”
),
oBtn2
=
document
。
querySelector
(
“#btn2”
);
let
uploadOpt
=
{
iframeName
:
‘iframe’
,
// iframe name and form target
action
:
‘/upload’
,
// 上傳介面
type
:
[
‘image/jpeg’
,
‘image/png’
,
‘video/mp4’
],
// 接收上傳檔案型別
endFn
:
function
()
{
// 回撥函式,接收上傳成功後端返回的資料
let
oPre
=
this
。
contentDocument
。
querySelector
(
“pre”
);
let
data
=
JSON
。
parse
(
oPre
。
innerHTML
)
// 後端返回的資料data
console
。
log
(
data
);
}
};
// 點選上傳按鈕
oBtn1
。
addEventListener
(
“click”
,
()
=>
fileUpload
。
init
(
uploadOpt
));
oBtn2
。
addEventListener
(
“click”
,
()
=>
fileUpload
。
init
(
uploadOpt
));
版本4:
版本3
點選按鈕選擇檔案開啟後直接進行上傳操作,但對於上傳進度我們是不清楚的,在這個版本我們需要加上相關進度的展示。展示進度常用一下方式來實現:
Flash
基於WebSocket長連線方式去實現
輪訓(前端不斷向後端請求上傳進度)
用AJAX實現檔案上傳,AJAX提供有相關進度的介面(
版本5
中會用此方法進行改進)
以上方案各有優缺點,下面我用輪訓的方式實現下。簡單來說就是後端提供介面,前端每次請求實時返回檔案的上傳進度。就像下面的程式碼,
version_04。html
中也有完整的程式碼展示:
setInterval
(
lx
,
500
);
function
lx
()
{
let
xhr
=
new
XMLHttpRequest
;
xhr
。
open
(
‘post’
,
‘/progress’
);
xhr
。
send
();
xhr
。
onreadystatechange
=
function
()
{
if
(
xhr
。
readyState
==
4
&&
xhr
。
status
==
200
)
{
oProgress
。
value
=
parseInt
(
xhr
。
responseText
);
(
xhr
。
responseText
===
“100”
)
&&
clearInterval
(
timer
);
}
}
}
版本5:
其實上面4個版本我們都是在利用form表單,然後submit幫我們自動上傳,這個版本我們嘗試用FormData的形式獲取上傳資料(關於什麼是FormData,參考MDN的說明,使用非常簡單),並結合AJAX的形式把資料傳給後臺,這樣進度展示也可以利用AJAX提供的API在前端獲取。
程式碼展示
function
UploadFile
()
{
this
。
oWrap
=
this
。
createEle
(
“div”
);
this
。
oBtn
=
this
。
createEle
(
“span”
);
}
// 建立標籤的函式
UploadFile
。
prototype
。
createEle
=
function
(
ele
)
{
return
document
。
createElement
(
ele
)
};
// 初始化頁面上的上傳按鈕
UploadFile
。
prototype
。
initBtn
=
function
(
opt
)
{
// btn
this
。
oBtn
。
className
=
“btn”
;
this
。
oBtn
。
innerHTML
=
“上傳檔案”
;
opt
。
cls
&&
(
this
。
oWrap
。
className
=
opt
。
cls
);
this
。
oWrap
。
appendChild
(
this
。
oBtn
);
document
。
body
。
appendChild
(
this
。
oWrap
);
};
// 建立FILE上傳框和進度條
UploadFile
。
prototype
。
initHtml
=
function
(
opt
)
{
this
。
oFile
=
this
。
createEle
(
“input”
);
this
。
oProgressWrap
=
this
。
createEle
(
“div”
);
this
。
oProgressCon
=
this
。
createEle
(
“span”
);
// file
this
。
oFile
。
type
=
“file”
;
this
。
oFile
。
style
。
display
=
“none”
;
opt
。
multiple
&&
(
this
。
oFile
。
multiple
=
opt
。
multiple
);
// progress
this
。
oProgressWrap
。
className
=
“progress-wrap”
;
this
。
oProgressCon
。
className
=
“progress-con”
;
this
。
oProgressWrap
。
appendChild
(
this
。
oProgressCon
);
opt
。
top
&&
(
this
。
oProgressCon
。
style
。
top
=
opt
。
top
+
“px”
);
this
。
oWrap
。
appendChild
(
this
。
oFile
);
this
。
oWrap
。
appendChild
(
this
。
oProgressWrap
);
};
// 用AJAX把資料傳給後臺
UploadFile
。
prototype
。
ajax
=
function
(
opt
)
{
let
xhr
=
new
XMLHttpRequest
,
_this
=
this
;
xhr
。
open
(
‘post’
,
‘/upload’
);
xhr
。
upload
。
addEventListener
(
“progress”
,
(
ev
)
=>
{
let
percent
=
(
ev
。
loaded
/
ev
。
total
)
*
100
+
“%”
;
this
。
oProgressCon
。
style
。
width
=
percent
;
});
xhr
。
send
(
opt
[
“formData”
]);
xhr
。
addEventListener
(
“readystatechange”
,
()
=>
{
if
(
xhr
。
readyState
===
4
&&
xhr
。
status
===
200
)
{
opt
。
cb
&&
opt
。
cb
(
JSON
。
parse
(
xhr
。
responseText
));
}
});
};
UploadFile
。
prototype
。
handle
=
function
(
opt
)
{
let
_this
=
this
;
this
。
oBtn
。
addEventListener
(
“click”
,
()
=>
{
// 下次點選的時候把上次建立的刪除
this
。
oFile
&&
this
。
oFile
。
remove
();
this
。
oProgressWrap
&&
this
。
oProgressWrap
。
remove
();
// 每次點選按鈕的時候把該建立的建立了,也可以在初始化的時候一次性把事辦齊
this
。
initHtml
(
opt
);
// 主動點選,彈出系統上傳框
this
。
oFile
。
click
();
// 監聽上傳框
this
。
oFile
。
addEventListener
(
“change”
,
()
=>
{
let
formData
=
new
FormData
();
// 透過FormData物件可以組裝一組用 XMLHttpRequest傳送請求的鍵/值對
formData
。
append
(
‘upload’
,
_this
。
oFile
。
files
[
0
]);
opt
[
“formData”
]
=
formData
;
this
。
ajax
(
opt
);
});
});
};
UploadFile
。
prototype
。
init
=
function
(
opt
)
{
opt
=
opt
||
{};
// 這裡可以搞一下預設引數的配置
this
。
initBtn
(
opt
);
this
。
handle
(
opt
);
};
/*
引數說明:
top: 進度條距離視窗頂部距離
cls: 外包裹class名字
multiple: 是否支援多選
cb: 上傳成功的回撥
accept: 支援上傳的檔案格式(未做)
還有一些細節要處理:
例如上一個還沒傳完,我又點選了相同的上傳按鈕這時候前端進度該如何處理
當碰到很大的檔案時,我們是否考慮切割上傳
前端是不是最好也把上傳預覽做一下
先到這裡,有空再來完善。。。
*/
let
btn1
=
new
UploadFile
();
let
btn2
=
new
UploadFile
();
btn1
。
init
({
top
:
0
,
cls
:
“wrap”
,
multiple
:
true
,
cb
:
function
(
data
)
{
console
。
log
(
data
);
}
});
btn2
。
init
({
top
:
“20”
});
檢視原始碼
Photo by Ian Stauffer