實現JS監聽文件下載完成的功能(解決不同瀏覽器的下載觸發 + a標籤base64url過長無法下載的問題)

一般來說,我們的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中,模擬點擊來進行下載。

但是需要注意的是

  1. a標籤的download經測試,僅在谷歌和火狐瀏覽器生效,在edge和IE下不觸發下載
  2. 如果以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()
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章