前端下載文件的5種方法的對比

NO IMAGE

前言

在前端站點上下載文件,這是一個極其普遍的需求,很早前就已經有各種解決方法了,為什麼還寫這麼老的文章,只是最近在帶一個新人,他似乎很多都一知半解,也遇到了我們必經問題之“不能下載txt、png等文件”的典型問題,我就給他總結下下載的幾個方式。順便分享出來,也許,真有人需要。

form表單提交

這是以前常使用的傳統方式,畢竟那個年代,沒那麼多好用的新特性呀。

道理也很簡單,為一個下載按鈕添加click事件,點擊時動態生成一個表單,利用表單提交的功能來實現文件的下載(實際上表單的提交就是發送一個請求)

來看下如何生成一個表單,生成怎麼樣的一個表單:

/**
* 下載文件
* @param {String} path - 請求的地址
* @param {String} fileName - 文件名
*/
function downloadFile (downloadUrl, fileName) {
// 創建表單
const formObj = document.createElement('form');
formObj.action = downloadUrl;
formObj.method = 'get';
formObj.style.display = 'none';
// 創建input,主要是起傳參作用
const formItem = document.createElement('input');
formItem.value = fileName; // 傳參的值
formItem.name = 'fileName'; // 傳參的字段名
// 插入到網頁中
formObj.appendChild(formItem);
document.body.appendChild(formObj);
formObj.submit(); // 發送請求
document.body.removeChild(formObj); // 發送完清除掉
}

優點

  • 傳統方式,兼容性好,不會出現URL長度限制問題

缺點

  • 無法知道下載的進度
  • 無法直接下載瀏覽器可直接預覽的文件類型(如txt/png等)

open或location.href

最簡單最直接的方式,實際上跟a標籤訪問下載鏈接一樣

window.open('downloadFile.zip');
location.href = 'downloadFile.zip';

當然地址也可以是接口api的地址,而不單純是個鏈接地址。

優點

  • 簡單方便直接

缺點

  • 會出現URL長度限制問題
  • 需要注意url編碼問題
  • 瀏覽器可直接瀏覽的文件類型是不提供下載的,如txt、png、jpg、gif等
  • 不能添加header,也就不能進行鑑權
  • 無法知道下載的進度

a標籤的download

我們知道,a標籤可以訪問下載文件的地址,瀏覽器幫助進行下載。但是對於瀏覽器支持直接瀏覽的txt、png、jpg、gif等文件,是不提供直接下載(可右擊從菜單裡另存為)的。

為了解決這個直接瀏覽不下載的問題,可以利用download屬性。

download屬性是HTML5新增的屬性,兼容性可以瞭解下 can i use download

總體兼容性算是很好了,基本可以區分為IE和其他瀏覽。但是需要注意一些信息:

  • Edge 13在嘗試下載data url鏈接時會崩潰。
  • Chrome 65及以上版本只支持同源下載鏈接。
  • Firefox只支持同源下載鏈接。

基於上面描述,如果你嘗試下載跨域鏈接,那麼其實download的效果就會沒了,跟不設置download表現一致。即瀏覽器能預覽的還是會預覽,而不是下載。

簡單用法:

<a href="example.jpg" download>點擊下載</a>

可以帶上屬性值,指定下載的文件名,即重命名下載文件。不設置的話默認是文件原本名。

<a href="example.jpg" download="test">點擊下載</a>

如上,會下載了一個名叫test的圖片

監測是否支持download

要知道瀏覽器是否支持download屬性,簡單的一句代碼即可區分

const isSupport = 'download' in document.createElement('a');

對於在跨域下不能下載可瀏覽的文件,其實可以跟後端協商好,在後端層做多一層轉發,最終返回給前端的文件鏈接跟下載頁同域就好了。

優點

  • 能解決不能直接下載瀏覽器可瀏覽的文件

缺點

  • 得已知下載文件地址
  • 不能下載跨域下的瀏覽器可瀏覽的文件
  • 有兼容性問題,特別是IE
  • 不能進行鑑權

利用Blob對象

該方法較上面的直接使用a標籤download這種方法的優勢在於,它除了能利用已知文件地址路徑進行下載外,還能通過發送ajax請求api獲取文件流進行下載。畢竟有些時候,後端不會直接提供一個下載地址給你直接訪問,而是要調取api。

利用Blob對象可以將文件流轉化成Blob二進制對象。該對象兼容性良好,需要注意的是

  • IE10以下不支持。
  • 在Safari瀏覽器上訪問Blob UrlObject URL當前是有缺陷的,如下文中通過URL.createObjectURL生成的鏈接。caniuse官網有指出

Safari has a serious issue with blobs that are of the type application/octet-stream

進行下載的思路很簡單:發請求獲取二進制數據,轉化為Blob對象,利用URL.createObjectUrl生成url地址,賦值在a標籤的href屬性上,結合download進行下載。

/**
* 下載文件
* @param {String} path - 下載地址/下載請求地址。
* @param {String} name - 下載文件的名字/重命名(考慮到兼容性問題,最好加上後綴名)
*/
downloadFile (path, name) {
const xhr = new XMLHttpRequest();
xhr.open('get', path);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function () {
if (this.status === 200 || this.status === 304) {
// const blob = new Blob([this.response], { type: xhr.getResponseHeader('Content-Type') });
// const url = URL.createObjectURL(blob);
const url = URL.createObjectURL(this.response);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
};
}

該方法不能缺少a標籤的download屬性的設置。因為發請求時已設置返回數據類型為Blob類型(xhr.responseType = 'blob'),所以target.response就是一個Blob對象,打印出來會看到兩個屬性sizetype。雖然type屬性已指定了文件的類型,但是為了穩妥起見,還是在download屬性值裡指定後綴名,如Firefox不指定下載下來的文件就會不識別類型。

大家可能會注意到,上述代碼有兩處註釋,其實除了上述的寫法外,還有另一個寫法,改動一丟丟。如果發送請求時不設置xhr.responseType = 'blob',默認ajax請求會返回DOMString類型的數據,即字符串。這時就需要兩處註釋的代碼了,對返回的文本轉化為Blob對象,然後創建blob url,此時需要註釋掉原本的const url = URL.createObjectURL(target.response)

優點

  • 能解決不能直接下載瀏覽器可瀏覽的文件
  • 可設置header,也就可添加鑑權信息

缺點

  • 兼容性問題,IE10以下不可用;Safari瀏覽器可以留意下使用情況

利用base64

這裡的用法跟上面用Blob大同小異,基本上思路是一樣的,唯一不同的是,上面是利用Blob對象生成Blob URL,而這裡則是生成Data URL,所謂Data URL,就是base64編碼後的url形式。

/**
* 下載文件
* @param {String} path - 下載地址/下載請求地址。
* @param {String} name - 下載文件的名字(考慮到兼容性問題,最好加上後綴名)
*/
downloadFile (path, name) {
const xhr = new XMLHttpRequest();
xhr.open('get', path);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function () {
if (this.status === 200 || this.status === 304) {
const fileReader = new FileReader();
fileReader.readAsDataURL(this.response);
fileReader.onload = function () {
const a = document.createElement('a');
a.style.display = 'none';
a.href = this.result;
a.download = name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
}
};
}

這裡額外提供個方法,該方法作用是,當你知道文件的全名(含後綴名),想要重命名,但是得後綴名一樣,來獲取後綴名。

function findType (name) {
const index = name.lastIndexOf('.');
return name.substring(index + 1);
}

優點

  • 能解決不能直接下載瀏覽器可瀏覽的文件
  • 可設置header,也就可添加鑑權信息

缺點

  • 兼容性問題,IE10以下不可用

未經允許,請勿私自轉載

相關文章

Flutter完整開發實戰詳解(二十、AndroidPlatformView和鍵盤問題)

從同一功能的八種實現,談談react中的邏輯複用進化過程

Android自定義控件|小紅點的三種實現(上)

全棧面試彙總週刊|第十一期