上一篇《H5拖放+FormData接口+NodeJS,完全異步文件上傳(一)》,我們走通了拖放文件上傳的全部流程,但離實際運用場景另有差異。這篇,我們來增加幾個實際運用場景必要的功用,向實際運用再走一步。
增加功用
- 顯現待上傳文件列表;
- 支撐移除待上傳文件;
- 運用upload.onprogress顯現上傳進度;
- 支撐中綴上傳;
upload.progress
XMLHttpRequest.upload要領返回一個 XMLHttpRequestUpload對象,用來示意上傳的進度。這個對象是不透明的,然則作為一個XMLHttpRequestEventTarget,能夠經由過程對其綁定事宜來追蹤它的進度。onprogres監聽數據傳輸舉行中(經由過程監聽這個事宜,可取得上傳進度)。
摘自MDN
完成思緒
拖放文件到上傳地區時,將文件保留在一個文件數組中,增加並顯現文件相干信息和移除按鈕,點擊移除按鈕刪除文件數組中對應的文件元素,點擊上傳按鈕,遍歷文件數組,最先上傳待上傳文件,此時點擊移除按鈕則中斷文件上傳。
代碼
//相干款式
.drop-area{
margin:auto;
width: 500px;
height: 500px;
border:1px pink dashed;
}
.close-btn{
cursor: pointer;
}
.close-btn:after{
float: right;
content: 'X';
color: red;
}
#fileList{
width: 95%;
}
.process-bar{
position: relative;
margin: 0 10px 0 10px;
width: 198px;
height: 18px;
display: none;
text-align: center;
line-height: 20px;
color: #6dbfff;
border-radius: 3px;
border: 1px solid #1483d8;
background: #fff;
}
.success .process-bar,
.uploading .process-bar{
display: inline-block;
}
.process-bar .process-text{
position: relative;
z-index: 1;
}
.process-bar .process-rate{
position: absolute;
width: 0;
height: 100%;
left: 0;
top: 0;
border-radius: 3px;
background: #1483d8;
}
.file-list .success .process-text,
.file-list .success .close-btn:after,
.file-list .error .process-text,
.file-list .error .close-btn:after{
display: none;
}
.success .process-bar :after,
.error .process-bar :after{
content:'success';
position: absolute;
margin: auto;
left: 0;
right: 0;
z-index: 2;
}
.error .process-bar:after{
content: "error";
color: red;
}
<!--HTML-->
<div class="drop-area" ondrop="drop_hander(event)" ondragover = "dragover_hander(event)"></div><!--監聽拖放DOM-->
<div id="fileList" class="file-list"></div><!--顯現待上傳文件列表-->
<button id="submit">上傳</button><!--上傳按鈕-->
//Javascript代碼
let filesSet = []; //文件保留數組
let fileList = document.getElementById('fileList'); //獵取顯現文件列表DOM
/**
* 建立一個新的空缺的文檔片斷frag
* 用於附加多個待上傳文件的DOM,可減小迴流
* https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment
*/
let frag = document.createDocumentFragment();
let barDom = createProccessBar(); //建立進度條DOM
let submit = document.getElementById("submit") //獵取提交按鈕
/* 拖動到安排地區時 */
function dragover_hander (event) {
/* 必需同時阻撓dragover和drop的默許事宜
否則會相應瀏覽器默許行動
瀏覽器器能顯現的文件會直接顯現,比方html文件、圖片文件
瀏覽器器不能顯現的文件會湧現文件下載彈窗
*/
event.preventDefault();
}
/*拖放完成事宜*/
function drop_hander (event) {
event.preventDefault(); //阻撓默許事宜
var files = event.dataTransfer.files; //經由過程dataTransfer對象獵取文件對象數組
for(let file of files) { //遍歷文件對象數組
//建立文件信息顯現DOM,並保留在file對象的element屬性中,用於後續操縱
file.element = createFileDom(file, filesSet.length)
//複製進度條DOM,並保留在file對象的processBar屬性中,用於後續操縱
file.processBar = filesSet.length?barDom.cloneNode(true):barDom;
//將進度條增加至文件信息顯現DOM中
file.element.appendChild(file.processBar);
//file文件對象增加到文件保留數組
filesSet.push(file);
//建立文件信息顯現DOM增加至文檔片斷frag
frag.appendChild(file.element);
}
//將文件列表增加至顯現文件列表的div
fileList.appendChild(frag)
}
/**
* 建立文件信息顯現DOM
* file 文件對象,用於獵取文件信息
* index 文件對象在數組中的索引,用於刪除
*/
function createFileDom (file, index) {
let p = document.createElement('p');
//file.name屬性能夠取得文件名稱
//有興緻的童鞋,能夠運用for...in輪迴檢察一下file對象的別的屬性值
let text = document.createTextNode(file.name);
let span = document.createElement("span");
span.setAttribute('index', index); //索引增加至按鈕的index屬性
span.className = 'close-btn';
p.appendChild(text);
p.appendChild(span);
return p; //返迴文件信息顯現DOM
}
/**
* 建立進度條DOM,
* 進度條DOM構造牢固,可運用clone(true)舉行複製
* @return {[type]} [description]
*/
function createProccessBar() {
let bar = document.createElement("span");
let rate = document.createElement("span");
let text = document.createElement("span");
bar.className = "process-bar";
rate.className = "process-rate";
text.className = "process-text";
text.innerText="0%";
bar.appendChild(text);
bar.appendChild(rate);
return bar;
}
//經由過程事宜代辦,監聽移除或中斷上傳
fileList.addEventListener('click', (evt)=>{
let index = evt.target.getAttribute('index'); //取得index屬性值
if (index) { //存在index屬性值,示意點擊了刪除按鈕
if (filesSet[index].unloading && filesSet[index].req) { //文件已上傳中
filesSet[index].req.abort(); //中斷上傳
filesSet[index].unloading = false; //將上傳中的狀況設置為false
} else { //未最先上傳
filesSet[index].element.remove(); //移除DOM
filesSet[index].element = null; //釋放對DOM的援用
filesSet[index].processBar = null;//釋放對DOM的援用
delete filesSet[index];//刪除文件數組中對應的元素
}
}
})
submit.addEventListener('click',function(){// 為上傳按鈕綁定事宜
//這裏運用for...in輪迴,恰好能夠防止對數組希罕元素的遍歷
for(let key in filesSet){
//假如正在上傳中或已上傳完成,不再上傳
if (filesSet[key].unloading || filesSet[key].uploaded) continue;
filesSet[key].unloading = true; //標記最先上傳
//建立一個文件上傳的Promise,並設置勝利及失利的回調
initUpload(filesSet[key]).then(file => {
//上傳勝利
file.element.className = "success"; //UI顯現勝利信息
file.uploaded = true; //標記上傳勝利
}).catch((file, err) => {
file.element.className = "error"; //UI顯現失利信息
//作廢最先上傳標記,點擊上傳按鈕將嘗試再次上傳
filesSet[key].unloading = false;
})
}
})
/**
* 返回一個文件上傳的Promise實例
* @param {[type]} file 要上傳的文件
*/
function initUpload(file){
return new Promise((res, rej) => {
let formData = new FormData();//聲明一個FormData實例
let req = new XMLHttpRequest();//建立XHR實例
let reta = file.processBar.querySelector('.process-rate');//取得進度條DOM
let text = file.processBar.querySelector('.process-text');//取得進度文本DOM
let pre;//保留上傳百分比
//監聽數據傳輸舉行中
req.upload.onprogress =function(data){
pre = (data.loaded/data.total*100).toFixed(2);//盤算百分比
reta.style.width = pre +'%';//修正進度條
text.innerText = pre +'%' ;//修正進度條文本
}
//監聽要求完成
req.onreadystatechange = function () {
if (req.readyState !==4 ) return ;
if (req.status === 200 ){
//完成,實行勝利回調
res(file, req)
} else {
//失利,實行失利回調
rej(file, req)
}
}
formData.append('file',file); //運用append要領增加文件到file鍵
req.open('POST', '/process_post'); //初始化要求
req.send(formData); //發送要求
file.req = req; //保留req對象,用於中斷要求
//形如顯現上傳進度
file.element.className = "uploading"
})
}
到這裏代碼就完畢了,完全代碼能夠檢察Github。由於是當地上傳,在測試的時刻能夠運用大一些的文件,或許限定一下上傳。
完畢語
這些新的API,使得文件拖放上傳變得簡樸起來。惋惜低版本的IE並不支撐,據說低版本的IE能夠運用Falsh來舉行文件上傳,詳細是怎樣完成的,要不我們下篇再來討論一下。