在web
開發中,若是你想讓用戶下載或者導出一個文件,應該怎麼作呢?
傳統的作法是在後端存儲或者即時生成一個文件來提供下載功能,這樣的優點是能夠作權限控制、數據二次處理,但缺點是須要額外發起請求、增大服務端壓力、下載速度慢。javascript
但隨着HTML5
的標準發佈,咱們已經可以作到只前端來下載各類文件了。css
在常規的HTTP應答中,Content-Disposition
消息頭指示回覆的內容該以何種形式展現,是之內聯的形式(即網頁或者頁面的一部分),仍是以附件的形式下載並保存到本地。在
HTTP
場景中,第一個參數或者是inline
(默認值,表示回覆中的消息體會以頁面的一部分或者整個頁面的形式展現),或者是attachment
(意味着消息體應該被下載到本地;大多數瀏覽器會呈現一個「保存爲」的對話框,將filename
的值預填爲下載後的文件名)。html
咱們在後端響應頭中只要設置該頭部信息,便可下載爲文件,而不是請求並展現:前端
Content-Type: text/html; charset=utf-8 Content-Disposition: attachment; filename="cool.html"
但須要注意的是,若是想要用這種方式下載文件,不能使用AJAX
的方式,而是應該新建一個<a>
標籤,模擬點擊下載。緣由爲處於安全性考慮,JavaScript
沒法與磁盤進行交互,所以AJAX獲得的內容將被保留在內存中,而不是磁盤上。java
Nginx
添加header
頭下載location ~ \.(jpg|jpeg|png|bmp|ico|gif|swf)$ { add_header Content-Disposition 'attachment; filename="cool.html"'; }
和後端同樣的原理,只不過頭部信息經過Nginx
統一添加。git
<a>
標籤的download
屬性此屬性指示瀏覽器下載URL
而不是導航到它,所以將提示用戶將其保存爲本地文件。若是屬性有一個值,那麼它將做爲下載的文件名使用。此屬性對容許的值沒有限制,可是/
和\
會被轉換爲下劃線。
URLs
。HTTP URL
須要位於同一源中,可是可使用 blob: URLs
和 data: URLs
,以方便用戶下載 JavaScript
方式生成的內容(例如使用在線繪圖的Web
應用建立的照片)。常規的<a>
標籤,用於連接的跳轉,如新的頁面,那麼若是咱們給<a>
標籤加上download
屬性,就能很簡單的讓用戶保存新的html
頁面。github
<a download="PHP實現併發請求.html" href="https://segmentfault.com/a/1190000016343861">PHP實現併發請求</a>
首先咱們須要瞭解一個特殊的數據格式:Blob
。web
Blob
數據Blob(Binary Large Object,二進制類型的大對象)
,表示一個不可變的原始數據的類文件對象,咱們上傳文件時經常使用的File
對象就繼承於Blob
,並進行了擴展用於支持用戶系統上的文件。json
咱們只能經過Blob()
構造函數來建立一個新的Blob
對象:segmentfault
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>
標籤,點擊便可下載咱們想要的文件:
<a href="blob:https://developer.mozilla.org/58702010-433d-4097-990f-e483d84cd02a" download="file.json">下載文件連接</a>
FileReader
讀取Blob
數據想要讀取Blob
數據的惟一方法是FileReader
。
FileReader
對象容許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩衝區)的內容,使用File
或Blob
對象指定要讀取的文件或數據。其中
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 && (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); } }, // 先建立<a>標籤,而後提供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 及其依賴組件。
@Oliveryoung
提供的其餘解決方案