一般來說,我們的js文件下載會使用window.location.href定位到後端的接口,後端生成文件返回,然後瀏覽器自動下載。這種方法最簡單,但是無法獲取下載成功的通知,在大文件生成和下載的時候,時間過長,用戶可能會重複進行下載的點擊,對服務器造成負擔。
因此要用另外的方式發送請求來監聽文件下載完成,此方法使用的是XMLHttpRequest來請求,可以監聽文件下載完成,另外一提,如果要對下載使用進度條監聽的話,可以使用設置cookie循環向後端請求獲取進度的方法,本文只討論前者。
不多bb,直接開始
這是我一開始的版本:
load = function() {
//填寫你的下載時加載的提示
}
disload = function() {
//下載完成後觸發,用來關閉提示框
}
getDownload = function(url) {
load();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // 也可用POST方式
xhr.responseType = "blob";
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (e) {
var headerName = xhr.getResponseHeader("Content-disposition");
var fileName = decodeURIComponent(headerName).substring(20);
var a = document.createElement('a');
a.download = fileName + ".xls";
a.href = e.target.result;
$("body").append(a); // 修復firefox中無法觸發click
a.click();
$(a).remove();
};
disload();
}
};
xhr.send()
};
這個原理是通過XMLHttpRequest發送請求,利用FileReader來讀取文件內容,關鍵是設置a標籤,將blob的文件內容轉換爲base64並放入a標籤的href中,模擬點擊來進行下載。
但是需要注意的是
- a標籤的download經測試,僅在谷歌和火狐瀏覽器生效,在edge和IE下不觸發下載
- 如果以base64爲a標籤的url地址的話,由於長度限制最多隻能下載大約2M的文件,再高的話就會下載失敗
第一個問題
對於ie等瀏覽器來說,有另外的一種方法來觸發下載。
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
//e.target.result 正是上面代碼中轉換後的baseUrl,這種方法要轉換爲blob
var blob = dataURLtoBlob(e.target.result);
navigator.msSaveBlob(blob, fileName + ".xls");
需要注意的是navigator.msSaveBlob在谷歌等瀏覽器中並不存在,在此,就解決了第一個問題
第二個問題
第二個問題其實也看需求,對於非常小的文件,比如小圖標等可以用base64作爲url地址,對於其他的大文件,可以將文件存入內存中,用虛擬內存地址指向。
var blob = dataURLtoBlob(e.target.result);
var a = document.createElement('a');
a.download = fileName + ".xls";
a.href = URL.createObjectURL(blob);
$("body").append(a); // 修復firefox中無法觸發click
a.click();
URL.revokeObjectURL(a.href);
$(a).remove();
URL.createObjectURL 將blob形式的文件存入並返回一個url以供下載。
而URL.revokeObjectURL則將該內存釋放。
至此,兩個問題解決,可以看到大文件根本不能使用base64,直接使用blob是最好的。因此合併起來就可以得到最終版本了:
最終版本
load = function() {
//填寫你的下載時加載的提示
}
disload = function() {
//下載完成後觸發,用來關閉提示框
}
getDownload = function(url) {
load();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // 也可用POST方式
xhr.responseType = "blob";
xhr.onload = function () {
if (this.status === 200) {
var blob = this.response;
if (navigator.msSaveBlob == null) {
var a = document.createElement('a');
var headerName = xhr.getResponseHeader("Content-disposition");
var fileName = decodeURIComponent(headerName).substring(20);
a.download = fileName;
a.href = URL.createObjectURL(blob);
$("body").append(a); // 修復firefox中無法觸發click
a.click();
URL.revokeObjectURL(a.href);
$(a).remove();
} else {
navigator.msSaveBlob(blob, fileName);
}
}
disload();
};
xhr.send()
};