前端實現文件下載和拖拽上傳

需求

  1. 頁面中增加下載示例按鈕
  2. 實現一塊區域能夠拖拽上傳word文件,限制文件大小2MB和文件類型,能顯示進度條,同時支持取消上傳。

文件下載

業務中要求的是示例放在靜態文件夾中,並不需要請求後臺。針對這種場景,筆者將介紹三種方法,分別是 window.open,form表單提交以及a標籤 下載。筆者將通過下載img和word文檔的例子,對這三種方法進行對比。

現構建dom結構如下:

<button onClick={this.windowOpen}>window.open</button>
<button onClick={this.formSubmit}>formSubmit</button>
<button onClick={this.aDownload}>aDownload</button>

方法一:使用 window.open :

import gakkiURL from './gakki.jpg';
import wordURL from './wordURL.doc';
windowOpen = () => {
    window.open(gakkiURL);
    //window.open(wordURL);
}

該方法在請求兩種文件時,具體表現爲:

img:新開網頁,然後顯示對應的img圖片。

word:下載該文件。

方法2:使用form表單的submit:

formSubmit = () => {
    let form = document.createElement('form');
    form.method = 'get';
    form.action = gakkiURL;
    //form.action = wordURL;
    //form.target = '_blank';    // form新開頁面
    document.body.appendChild(form); // form表單做出提交操作要先加入到dom樹中
    form.submit();
    document.body.removeChild(form);
}

該方法在請求兩種文件時,具體表現爲:

img:form在不設置target時,會在當前頁面打開url,顯示圖片。

word:下載該文件。

從上述兩種方法可以看出,在請求對應的url時, 瀏覽器針對不同的MIME類型會選擇不同的處理方式 。在請求img、txt等格式時,瀏覽器會打開對應的文件,而不是下載。如果想要img這些格式也下載呢?此時就需要方法三。

方法3:使用a標籤:

// 使用a標籤
aDownload = () => {
    const a = document.createElement('a');
    a.href = gakkiURL;
    //a.href = wordURL;
    //a.download = 'gakki.jpg';
    a.click();
}

a標籤在不加download屬性時表現同上兩種方法,而在加了 download 屬性後,可成功觸發img等格式的下載。

download:

該屬性可以設置一個值來規定下載文件的名稱。所允許的值沒有限制,瀏覽器將自動檢測正確的文件擴展名並添加到文件 (.img, .pdf, .txt, .html, 等等)。

最終對比效果:

文件拖拽上傳

文件上傳

常用方法是使用 type="file" 的input標籤觸發下載,然後使用formData傳輸數據,代碼如下:

// 點擊上傳文檔
handleClick = () => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document'; // word文件對應的MIME類型
    input.onchange = (e) => {
        const file = e.target.files.items(0);// files[0]也行
        console.table(file);
        // 檢查文檔格式
        if (!this.checkDocument()) {
            e.target.value = '';
            return;
        }
        // 上傳文檔
        this.uploadDocument(file);
    };
    input.click();
};

accept :表明input接受的文件的MIME類型。.doc和.docx相應的MIME類型在源碼中已標明。

fileList對象可通過items或數組索引的形式獲得對應的file對象。file對象常用的屬性有:lastModified、type、name和size。可通過這些屬性自定義檢查文檔格式。

檢查文檔的代碼checkDocument如下:

// 文檔檢查
checkDocument = file => {
    const accept = ['.doc', '.docx'];
    const index = file.name.lastIndexOf('.');
    if (index < 0 || accept.indexOf(file.name.substr(index).toLowerCase()) < 0) { // 檢查文件類型
        Message.error('暫不支持該文件格式');
        return false;
    }
    if (file.size > 2 * 1024 * 1024) { // 檢查文件大小
        Message.error('文檔大於2MB,上傳失敗');
        return false;
    }
    return true;
};

之後是上傳文檔uploadDocument:

// 上傳文檔
uploadDocument = file => {
    const index = file.name.lastIndexOf('.');
    const fileName = file.name.slice(0, index);
    const formData = new FormData();
    formData.append('file', file);
    // ajax、fetch或axios等方式上傳
    ...
};

上傳文檔後需要去獲取上傳進度顯示進度條,下面將對 ajax、fetch和axios對progress事件的支持情況 分別予以介紹。

Ajax

原生支持progress事件,可用於獲取上傳進度和下載進度,分別爲 xhr.upload.onprogress 和 xhr.onprogress 事件。代碼如下:

xhr.upload.onprogress = ev => console.log((ev.loaded / ev.total) * 100)

另外可以使用 xhr.abort() 取消文件上傳。

Fetch

不支持progress事件,所以無法獲取上傳進度。但是筆者在查閱資料時發現由於res.body是可讀字節流對象,所以可以使用res.body對象支持的 getReader() 屬性獲得 下載進度 ,具體文獻請參考 jakearchibald.com/2016/stream… 。此處代碼與上傳的需求無關,僅作爲fetch的相關拓展,可直接跳過這一段。

res.body.getReader() 方法用於讀取響應的原始字節流,該字節流是可以循環讀取的,直至body內容傳輸完成;

fetch(url, options).then(res => {
    let reader = res.body.getReader();
    let loaded = 0;

    // read()方法返回一個promise,接受值時resolve。
    reader.read().then(function processResult(result) {
        // result對象有兩個屬性:
        // done:完成時爲true
        // value 數據
        if (result.done) { // 完成時退出循環
            console.log("Fetch complete");
            return;
        }

        loaded += result.value.length;// 長度,單位:字節
        console.log('Received', loaded, 'bytes of data so far');

        // 循環讀取
        return reader.read().then(processResult);
    });
});

Axios

通過 onUploadProgress 和 onDownloadProgress 實現上傳和下載。

onUploadProgress(ev) => {
    length = Math.round((ev.loaded / ev.total) * 100);
    console.log(length);
}

axios使用 cancel token 取消請求

var CancelToken = axios.CancelToken;
var source = CancelToken.source();

axios.get(url, {
  cancelToken: source.token
})

source.cancel();//取消請求

總結

ajax和axios對progress事件都進行了很好地支持,而fetch由於缺少對progress事件的支持在這裏無法使用。

拖拽事件

實現了點擊上傳、獲得上傳進度以及取消上傳等功能後,接下來要完成的是實現拖拽上傳。實現前,我將對有關事件進行介紹。 首先是拖拽物體時發生的事件: onDragStart , onDrag 和 onDragEnd ,事件與被拖拽的物體有關。

onDragStart :拖拽開始

onDrag :拖拽中持續觸發

onDragEnd :拖拽結束,無論是否可以放置均觸發事件

然後是放置文件時要觸發的事件: onDragEnter , onDragOver , onDragLeave 和 onDrop ,事件與要拖放進的區域有關。

onDragEnter :拖拽的物體進入時觸發

onDragOver :拖拽的物體在區域中拖動時持續觸發

onDragLeave :拖拽的物體離開區域時觸發

onDrop :拖拽的物體放置在區域中時觸發

項目中爲了能夠拖拽word文檔,需要在容器上取消該容器默認的onDragEnter和onDragOver事件,這是因爲:

事件的偵聽器 dragenter 或 dragover 事件被用來表示有效的 drop 目標,也就是拖放項目可能被 dropped 的地方。web頁面或應用程序的大多數區域都不是 drop 數據的有效位置。因此,這些事件的默認處理是不允許出現 drop。

如果您想要允許 drop,您必須通過取消事件來防止默認的處理。您可以通過從attribute-defined 事件監聽器返回 false ,或者通過調用事件的 preventDefault 方法來實現這一點。後者在一個單獨的腳本中定義的函數中可能更可行。

dom結構如下:

<div
    styleName="dropbox"
    onDragOver={this.preventDefault}
    onDragEnter={this.preventDefault}
    onDrop={this.handleDrop}
    >
    <div styleName="word-img" />
    {this.renderBtnByUpload(this.state.uploadStatus)} // 根據上傳狀態決定是"上傳文件"還是"取消上傳"
</div>

在將文件拖拽到內容區放置後,可以通過 dataTransfer 對象獲得file信息。最終的handleDrop事件如下:

// 拖拽上傳
handleDrop = (e) => {
    const file = e.dataTransfer.files[0];
    if (e.dataTransfer.files.length > 1) {
        Message.error('僅支持上傳一個word文件');
        return;
    }
    if (!this.checkDocument(file)) {
        // 上傳失敗直接退出
        e.target.value = '';
        return;
    }
    this.uploadDocument(file); // 上傳文件
}

總結

最終,實現的總體思路就是,首先構建放置文件的容器,然後給該容器取消默認的 onDragOver和 onDragEnter 事件,當拖拽文件到容器中時通過 dataTransfer.files 拿到文件並上傳,使用ajax或axios等方式提供的progress事件拿到長度,將該長度傳到progressBar組件中,最後展示出來。

原文鏈接:https://www.jianshu.com/p/01c...

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