H5拖放異步文件上傳之二——上傳進度監聽

  上一篇《H5拖放+FormData接口+NodeJS,完全異步文件上傳(一)》,我們走通了拖放文件上傳的全部流程,但離實際運用場景另有差異。這篇,我們來增加幾個實際運用場景必要的功用,向實際運用再走一步。

增加功用

  1. 顯現待上傳文件列表;
  2. 支撐移除待上傳文件;
  3. 運用upload.onprogress顯現上傳進度;
  4. 支撐中綴上傳;

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來舉行文件上傳,詳細是怎樣完成的,要不我們下篇再來討論一下。
  
  

    原文作者:shufujia
    原文地址: https://segmentfault.com/a/1190000014398758
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞