- 蘇格團隊
- 作者:WDZ
- 交流QQ群:855833773
- 歡迎加入我們的團隊,微信聯絡方式:foreverpx_cjl
筆者在業務中碰到了需要下載示例和拖拽上傳並實現進度條的功能,針對過程中遇到的問題,筆者進行了相應的總結。
需求
- 頁面中增加下載示例按鈕
- 實現一塊區域能夠拖拽上傳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元件中,最後展示出來。