前端js實現字符串/圖片/excel文件下載


web開發中,如果你想讓用戶下載或者導出一個文件,應該怎么做呢?
傳統的做法是在后端存儲或者即時生成一個文件來提供下載功能,這樣的優勢是可以做權限控制、數據二次處理,但缺點是需要額外發起請求、增大服務端壓力、下載速度慢。

但隨着HTML5的標准發布,我們已經能夠做到只前端來下載各種文件了。

后端響應式下載

在常規的HTTP應答中, Content-Disposition 消息頭指示回復的內容該以何種形式展示,是以內聯的形式(即網頁或者頁面的一部分),還是以附件的形式下載並保存到本地。

HTTP場景中,第一個參數或者是inline(默認值,表示回復中的消息體會以頁面的一部分或者整個頁面的形式展示),或者是attachment(意味着消息體應該被下載到本地;大多數瀏覽器會呈現一個“保存為”的對話框,將filename的值預填為下載后的文件名)。

我們在后端響應頭中只要設置該頭部信息,即可下載為文件,而不是請求並展示:


Content-Type: text/html; charset=utf-8
Content-Disposition: attachment; filename="cool.html"

但需要注意的是,如果想要用這種方式下載文件,不能使用AJAX的方式,而是應該新建一個<a>標簽,模擬點擊下載。原因為處於安全性考慮,JavaScript無法與磁盤進行交互,因此AJAX得到的內容將被保留在內存中,而不是磁盤上。

前端下載:<a>標簽的download屬性

此屬性指示瀏覽器下載 URL而不是導航到它,因此將提示用戶將其保存為本地文件。如果屬性有一個值,那么它將作為下載的文件名使用。此屬性對允許的值沒有限制,但是 /\會被轉換為下划線。
  1. 此屬性僅適用於同源 URLs
  2. 盡管HTTP URL需要位於同一源中,但是可以使用 blob: URLsdata: URLs ,以方便用戶下載 JavaScript 方式生成的內容(例如使用在線繪圖的Web應用創建的照片)。

常規的<a>標簽,用於鏈接的跳轉,如新的頁面,那么如果我們給<a>標簽加上download屬性,就能很簡單的讓用戶保存新的html頁面。


&lt;a download="PHP實現並發請求.html" href="https://segmentfault.com/a/1190000016343861"&gt;PHP實現並發請求&lt;/a&gt;

生成並下載字符串文件

首先我們需要了解一個特殊的數據格式:Blob

Blob數據

Blob(Binary Large Object,二進制類型的大對象),表示一個不可變的原始數據的類文件對象,我們上傳文件時常用的File對象就繼承於Blob,並進行了擴展用於支持用戶系統上的文件。

我們只能通過Blob()構造函數來創建一個新的Blob對象:

Blob(blobParts[, options])

// 創建一個json類型的Blob對象,支持傳入同類型數據的一個數組
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)],
  {type : 'application/json'});

// 此時blob的值
// Blob(22) {size: 22, type: 'application/json'}

Blob對象存在兩個只讀屬性:

size: Blob 對象中所包含數據的大小(字節)。
type: 一個字符串,表明該Blob對象所包含數據的MIME類型。如果類型未知,則該值為空字符串。

URL對象和下載字符串文件

URL 接口是一個用來創建 URLs 的對象,包含兩個靜態方法:

objectURL = URL.createObjectURL(blob)
創建一個 URL(DOMString),包含一個唯一的blob鏈接(該鏈接協議為以blob:,后跟唯一標識瀏覽器中的對象的掩碼)。這個 URL 的生命周期和創建它的窗口中的 document 綁定。

URL.revokeObjectURL(objectURL)
銷毀之前使用URL.createObjectURL()方法創建的URL實例。瀏覽器會在文檔退出的時候自動釋放它們,但是為了獲得最佳性能和內存使用狀況,你應該在安全的時機主動釋放掉它們。


var url = URL.createObjectURL(blob);
// 此時url的值,跟document綁定,所以每個頁面創建的字符串均不同
// blob:https://developer.mozilla.org/defe53c2-2882-43c6-b275-db2a57959789

此時,我們在頁面中創建一個新<a>標簽,點擊即可下載我們想要的文件:


&lt;a href="blob:https://developer.mozilla.org/58702010-433d-4097-990f-e483d84cd02a" download="file.json"&gt;下載文件鏈接&lt;/a&gt;

FileReader讀取Blob數據

想要讀取Blob數據的唯一方法是FileReader

FileReader 對象允許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩沖區)的內容,使用 FileBlob 對象指定要讀取的文件或數據。

其中File對象可以是來自用戶在一個<input>元素上選擇文件后返回的FileList對象,也可以來自拖放操作生成的 DataTransfer對象,還可以是來自在一個HTMLCanvasElement上執行mozGetAsFile()方法后返回結果。

該對象包含3個屬性:

FileReader.error
一個DOMException,表示在讀取文件時發生的錯誤 。

FileReader.readyState
表示FileReader狀態的數字。取值如下:


常量名    值    描述
EMPTY    0    還沒有加載任何數據.
LOADING    1    數據正在被加載.
DONE    2    已完成全部的讀取請求.

FileReader.result
文件的內容。該屬性僅在讀取操作完成后才有效,數據的格式取決於使用哪個方法來啟動讀取操作。

包含6個事件處理:onabort,onerror,onload,onloadstart,onloadend,onprogress,這些不再詳細說明,因為 FileReader 繼承自EventTarget,所以所有這些事件也可以通過addEventListener方法使用。

包含5個方法:

FileReader.abort()
中止讀取操作。在返回時,readyState屬性為DONE。

FileReader.readAsArrayBuffer()
開始讀取指定的 Blob中的內容, 一旦完成, result 屬性中保存的將是被讀取文件的 ArrayBuffer 數據對象.

FileReader.readAsBinaryString()
開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含所讀取文件的原始二進制數據。

FileReader.readAsDataURL()
開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含一個data: URL格式的字符串以表示所讀取文件的內容。

FileReader.readAsText()
開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含一個字符串以表示所讀取的文件內容。

因此我們可以直接讀取Blob對象的數據:


var reader = new FileReader();
reader.addEventListener("loadend", function() {
   console.log(reader.result);
});
reader.readAsDataURL(blob);
// 此時result的值
// data:application/json;base64,ewogICJoZWxsbyI6ICJ3b3JsZCIKfQ==
reader.readAsText(blob);
// 此時result的值
// {
//     "hello": "world"
// }

下載圖片

除了下載手動生成的字符串或對象,我們還能提供下載圖片的功能,一方面能用於支持Canvas繪圖的保存功能,一方面能提供批量下載圖片等高級功能。

除了瀏覽器自帶的右鍵保存,我們還可以這么做來下載圖片:


// 通過src獲取圖片的blob對象
function getImageBlob(url, cb) {
    var xhr = new XMLHttpRequest();
    xhr.open("get", url, true);
    xhr.responseType = "blob";
    xhr.onload = function() {
        if (this.status == 200) {
            cb(this.response);
        }
    };
    xhr.send();
}

let reader = new FileReader();
reader.addEventListener("loadend", function() {
   console.log(reader.result);
});
getImageBlob('https://cdn.segmentfault.com/v-5c4ec07f/global/img/user-64.png', function(blob){
    // 讀取來看下下載的內容
    reader.readAsDataURL(blob);
    // 最終生成的字符串
    // data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAA...
    // 生成下載用的URL對象
    let url = URL.createObjectURL(blob);
    // 生成一個a標簽,並模擬點擊,即可下載,批量下載同理
    let aDom = aDom = document.createElement('a');
    aDom.href = url;
    aDom.download = 'download.json';
    aDom.text = '下載文件';
    document.getElementsByTagName('body')[0].appendChild(aDom);
    aDom.click();
});

下載excel文件等

如果你明白了下載的原理,那么所有的內容都能夠理解,只不過是轉換成對應的格式而已,當然,復雜格式的文檔不需要你自己去配置,可以引入第三方庫,在excel文檔方面我選擇用 tableExport庫


// 引入CDN文件
'https://cdn.bootcss.com/xlsx/0.14.1/xlsx.core.min.js',
'https://cdn.bootcss.com/FileSaver.js/2014-11-29/FileSaver.min.js',
'https://cdn.bootcss.com/TableExport/5.2.0/js/tableexport.min.js'

// 綁定下載事件,這個是我自己的場景下代碼,可能不適合大家,具體的參考官方文檔
const tableDom = $('#table');
$('.table-exportBtn', tableDom).on('click', function () {
    const tableExport = tableDom.tableExport({
        formats: ['xlsx', 'txt'],
        filename: '表格下載',
        exportButtons: false
    });
    const type = $(this).data().type;
    const exportData = tableExport.getExportData()[tableDom[0].id][type];
    const {data, mimeType, filename, fileExtension, merges, RTL, sheetname} = exportData;
    // 源碼里才能看到完整參數,官方文檔沒有寫全,導致下載的文件格式錯誤
    tableExport.export2file(data, mimeType, filename, fileExtension, merges, RTL, sheetname);
});

默認的方法會自動生成下載按鈕,但如果你想自定義下載功能,參考 exportButtons: false 設置 一節,但這個文檔有問題,export2file參數不完整,導致下載的xlsx文件一直格式錯誤,通過查看源碼,需要寫全參數才可以,上面的示例里已經寫出。

tableExport庫源碼

我們可以看下tableExport導出文件的核心代碼,其導出為excel格式比較復雜,由xlsx.core.min.js來完成:


/**
     * Exports and downloads the file
     * @memberof TableExport.prototype
     * @param data {String}
     * @param mime {String} mime type
     * @param name {String} filename
     * @param extension {String} file extension
     * @param merges {Object[]}
     * @param RTL {Boolean}
     */
    export2file: function(data, mime, name, extension, merges, RTL, sheetname) {
      var format = extension.slice(1);
      data = this.getRawData(data, extension, name, merges, RTL, sheetname);

      if (_isMobile &amp;&amp; (format === _FORMAT.CSV || format === _FORMAT.TXT)) {
        // 拼湊指定格式的data:類型 URI
        var dataURI = "data:" + mime + ";" + this.charset + "," + data;
        this.downloadDataURI(dataURI, name, extension);
      } else {
        // TODO: error and fallback when `saveAs` not available
        saveAs(new Blob([data], { type: mime + ";" + this.charset }), name + extension, true);
      }
    },
    // 先創建&lt;a&gt;標簽,然后提供href和download屬性,並模擬點擊
    downloadDataURI: function(dataURI, name, extension) {
      var encodedUri = encodeURI(dataURI);
      var link = document.createElement("a");
      link.setAttribute("href", encodedUri);
      link.setAttribute("download", name + extension);
      document.body.appendChild(link);
      link.click();
    },

xlsx文件導出導出

還沒有仔細研究,感興趣的可以查看其js-xlsx Github項目

第三方庫

上面我們主要講了下載背后的原理,你可以自己封裝,也可以使用現成的第三方庫,如 download.js ,這個能提供大部分常用數據的下載;但如果你是要下載表格數據為excel格式,還是推薦 tableExport.js 及其依賴組件。

參考資料

  1. MDN-a: https://developer.mozilla.org...
  2. MDN-blob: https://developer.mozilla.org...
  3. 掘金-細說Web API中的Blob:https://juejin.im/post/59e35d...
  4. MDN-URL: https://developer.mozilla.org...
  5. MDN-FileReader: https://developer.mozilla.org...
  6. 博客園-js 獲取圖片url的Blob值並預覽:https://www.cnblogs.com/tujia...
  7. tableExport文檔:https://tableexport.v5.travis...
  8. 感謝 @Oliveryoung 提供的其他解決方案
  9. MDN-Content-Disposition: https://developer.mozilla.org...
  10. Ajax請求無法下載文件的原因: https://blog.csdn.net/w405722...
  11. Github-download.js: https://github.com/rndme/down...

來源:https://segmentfault.com/a/1190000018143902


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM