這應該是你見過的最全前端下載總結
本身整理的一些項目中遇到過的關於上傳和下載的一些Demo,大前端系列(也就是純前端 + node端完成的下載,只要獲取到數據下載工做全是前端來作),僅供給位看官參考,避免踩坑,即插即用,歡迎fork和star🌟,爲這個倉庫添磚加瓦~(P.S. 我的認爲若是沒寫過上傳下載其實仍是挺麻煩的,這個基本能覆蓋大部分場景了~)html
純前端下載前端
node端下載node
在這裏怕你們沒有耐心看下去,放一個強烈推薦的filesaver的Demo動態圖linux
上傳和下載我的認爲在前端開發時稍微複雜一丟丟,須要額外處理一些事情而不是直接獲取數據渲染頁面,因此想着把平時遇到過的一些場景整理一下分享出來,大牛繞過,不喜勿噴~我平時在項目中接觸的也就是一些上傳圖片,上傳安裝包,下載圖片,下載安裝包以及整理數據生成excel文件下載下來。暫時尚未接觸過其餘類型的,因此本項目可能有必定的侷限性,只是給你們提供一種思路或者方案,有其餘想法歡迎評論~git
顧名思義,純前端實現,也就是不依賴於任何後端。不過這種方式有必定的侷限性,好比下載類型,寫法,數據形式等等。可是既然不依賴與後端,在可接受範圍以內仍是很推薦使用的,畢竟簡單啊~github
說到簡單,那麼最簡單的就是這個了。那就是基於<a>
標籤的下載文件方式,真的是超級簡單。使用方法以下:express
href: 文件的絕對/相對地址 download: 文件名(可省略,省略後瀏覽器自動識別源文件名) <a href='xxx.jpg' download='file.jpg'>下載jpg圖片</a>
那麼既然這麼簡單,那確定是存在問題的。npm
上面這張圖片是官方提供的兼容性,目前只有FireFox和Chrome支持download屬性。至少這兩個對於開發者來講不陌生,佔有量也很大,因此也還能夠吧,可是接下來我又嘗試了一下這兩個瀏覽器的兼容性狀況。
上面這張,是FireFox瀏覽器最新版,能夠看到點擊下載文件會彈出一個對話框,以後點擊保存文件才能夠進行下載,同時只能下載不能被瀏覽器打開的文件類型,如圖片、文本文件、html文件這種能夠被打開的文件,是沒法被下載的直接在瀏覽器進行預覽。
上面這張,是Chrome最新版,與FireFox相同,對於圖片文件和文本文件這種能夠被瀏覽器打開的文件不會被下載,而excel和安裝包這種文件是能夠被直接下載的,無需進行任何二次確認操做。json
確定能啊~爲何呢?其實a標籤的href屬性還能夠接受除了相對和絕對路徑以外的其餘形式Url,好比下面咱們要用到的DataUrl和BlobUrl。咱們使用這種形式,就可讓瀏覽器不預覽而直接下載圖片了,固然了操做起來更麻煩一些了就。canvas
// 首先,圖片轉base64 // ./util.js // html頁面,將a標籤href屬性動態賦值爲dataUrl <a id='downloadDataUrl' class="button is-dark">下載data:Url圖片</a> ... <script> const image = new Image(); image.setAttribute("crossOrigin",'Anonymous'); image.src = '../files/test-download.png' + '?' + new Date().getTime(); image.onload = function() { const imageDataUrl = image2base64(image); const downloadDataUrlDom = document.getElementById('downloadDataUrl'); downloadDataUrlDom.setAttribute('href', imageDataUrl); downloadDataUrlDom.setAttribute('download', 'download-data-url.png'); downloadDataUrlDom.addEventListener('click', () => { console.log('下載文件'); }); } </script>
以下圖,能夠看到再也不是預覽文件,而是直接下載文件了。這裏面有一些坑,好比canvas.toDataUrl的一些問題以及解決辦法,我就很少說了,你們本身去看看。
總體邏輯更復雜了,首先 文件 -> base64(dataUrl) -> blob -> blobUrl
// 第一步:首先須要將文件轉換成base64,方法上面同樣 // 第二步:將base64轉換成blob數據 // DataUrl 轉 Blob數據 function dataUrl2Blob(dataUrl) { var arr = dataUrl.split(','), mime = arr[0].match(/:(.*?);/)[1], bStr = atob(arr[1]), n = bStr.length, unit8Array = new Uint8Array(n); while (n--) { unit8Array[n] = bStr.charCodeAt(n); } return new Blob([unit8Array], { type: mime }); } // 第三步: 將blob數據轉換成BlobUrl URL.createObjectURL(imageBlobData); // 完整代碼 <a id='downloadBlobUrl' class="button is-danger">下載blobUrl圖片</a> ... const image2 = new Image(); image2.setAttribute("crossOrigin",'Anonymous'); image2.src = '../files/test-download.png' + '?' + new Date().getTime(); image2.onload = function() { const imageDataUrl = image2base64(image2); const imageBlobData = dataUrl2Blob(imageDataUrl); const downloadDataUrlDom = document.getElementById('downloadBlobUrl'); downloadDataUrlDom.setAttribute('href', URL.createObjectURL(imageBlobData)); downloadDataUrlDom.setAttribute('download', 'download-data-url.png'); downloadDataUrlDom.addEventListener('click', () => { console.log('下載文件'); }); }
【總結】: Chrome在兼容性上更勝一籌,可是兩者整體來講都存在一些問題,不能直接下載圖片和文本文件,可是畢竟這麼簡潔,你沒進行任何多餘的操做,存在問題合情合理。同時,上面的幾種方式也看到了,dataUrl適合圖片的下載,而blobUrl雖然要麻煩一些,可是對於文本文件的下載仍是很是有用的, 你能夠直接把要下載的內容轉換成blob數據,而後轉換成blobUrl進行下載,適用於.txt,.json等文件類型。【建議】: 若是下載的需求是特殊文件類型,如安裝包,excel文件,而且能夠存放在CDN又一個可訪問的url連接。那麼這種方式很是完美,固然,若是你能夠接受上面所說的兼容性問題。同時若是你採用dataUrl或者blobUrl的時候,因爲存在不少問題,好比cors之類的事情,建議可使用這種方法,可是須要配合後端,也就是後端幫你轉換好,你直接拿轉換好的url來下載就好了。
上面這兩種很是好理解,就是在另外一個窗口或者當前地址欄地址指向下載連接,下載連接要求是dataUrl或者blobUrl。只不過,iframe是更高級一些,也就是能夠幫助咱們作到無閃下載,做爲開發者你們應該都懂,我就很少BB了。
從上面這個動圖,能夠看出來,這個方法其實還不如<a>
標籤下載,爲何這麼說呢,會由於a標籤方法雖然會預覽瀏覽器能夠預覽的文件,可是若是進行適當轉化,仍是能進行下載的。可是location這種方法不管是dataUrl仍是blobUrl,只要是圖片、文本文件以及pdf等全部瀏覽器能夠打開的文件,都會直接給你預覽,只能下載那些瀏覽器不支持預覽的那些文件。因此Just so so了。
本質很簡單,就是不讓當前瀏覽器窗口執行下載操做,而是另開一個iframe進行文件的下載。可是這個iframe是用戶不可見的~
這裏須要注意,若是是純前端,建議不要進行圖片等瀏覽器可打開的文件下載,由於隱藏iframe裏打開你也看不到,也就是他的問題仍是上面那些。能夠進行excel、zip以及各類資源文件的下載。
// 無閃現下載excel function download(url) { const iframe = document.createElement('iframe'); iframe.style.display = 'none'; function iframeLoad() { console.log('iframe onload'); const win = iframe.contentWindow; const doc = win.document; if (win.location.href === url) { if (doc.body.childNodes.length > 0) { // response is error } iframe.parentNode.removeChild(iframe); } } if ('onload' in iframe) { iframe.onload = iframeLoad; } else if (iframe.attachEvent) { iframe.attachEvent('onload', iframeLoad); } else { iframe.onreadystatechange = function onreadystatechange() { if (iframe.readyState === 'complete') { iframeLoad; } }; } iframe.src = ''; document.body.appendChild(iframe); setTimeout(function loadUrl() { iframe.contentWindow.location.href = url; }, 50); }
若是你在項目裏須要進行無閃現下載,什麼都不用作,只須要調用
download(url)
,便可進行無閃現下載~親測可用
FileSaver的下載方式徹底是前端(Client-Side)的下載方式,它是基於Blob進行下載的,固然由於是基於前端下載,因此瀏覽器下載會有必定的限制,也就是Blob數據的大小不能過大,看看官網給的相關參數:
能夠看到,基本對於支持的瀏覽器來講,大小能夠達到 500MB+,應該已經能夠知足大部分需求了。若是文件確實很大,官網給出了替代方案 StreamSaver,沒去研究過這個,不過做者既然推薦可能也很好,感興趣的能夠去看看。
前面講到了,FileSaver是基於Blob的,其實並不許確,能夠看一下官網:
其實它支持Blob、File和Url進行下載,可是若是基於url了我也不必用FileSaver了,那個<a>
標籤也挺好的是不?而後基於File通常都是特定場景,好比上傳的時候,纔會用到FileReader之類的API,說實話我也沒怎麼用過,都是封裝的,因此這裏也不作介紹。開頭也說過了,但願小夥伴能夠給這個倉庫添加東西啊,能夠增長本身的下載Demo到這裏,很是歡迎
因此我這篇文章討論的下載,就是基於Blob。首要工做就是將文件轉換成Blob數據。下面幾個例子都是這樣:
這個Demo簡單點的話其實能夠直接用canvas畫一個image在頁面上,而後再進行下載,可是那樣還不如直接下載圖片了,因此麻煩一些,寫一個canvas白板,而後下載咱們本身繪製的內容而且起名字進行下載。
// 生成下載的文件名 function generateFilename(id, mime) { const filename = document.getElementById(id).value || document.getElementById(id).placeholder; return filename + mime; } const canvasDownloadDom = document.getElementById('download-canvas'); canvasDownloadDom.addEventListener('click', () => { const canvas = document.getElementById('canvas'); const filename = generateFilename('canvasName', '.png'); if (canvas.toBlob) { // 調用方法將canvas轉換成blob數據 canvas.toBlob( function (blob) { // 調用FileSaver方法下載 saveAs(blob, filename); }, 'image/png' ); } });
代碼很是簡單,感興趣的小夥伴能夠去看看每一個插件內部的代碼。我這裏就是應用級別的示例了。
直接下載圖片就是將圖片轉換成Blob數據,而後進行下載。
// FileSaver 下載文件 const image = new Image(); image.setAttribute("crossOrigin",'Anonymous'); image.src = '../files/test-download.png' + '?' + new Date().getTime(); image.onload = function() { const imageDataUrl = image2base64(image); const imageBlobData = dataUrl2Blob(imageDataUrl); const downloadImageDom = document.getElementById('download-image'); downloadImageDom.addEventListener('click', () => { saveAs(imageBlobData, 'test-download.png'); }); }
這代碼就更簡單了,就是前面
<a>
標籤下載Blob數據的代碼,數據轉換是同樣的,只不過下載使用的是FileSaver。
下載文本文件就更容易了,由於JavaScript支持直接將字符串構形成Blob對象。
const textBlob = new Blob(["your target string"], {type: "text/plain;charset=utf-8"});
下載下來的txt文件長這樣:
// FileSaver 下載文本文件 const txtDownloadDom = document.getElementById('download-txt'); txtDownloadDom.addEventListener('click', () => { const textarea = document.getElementById('textarea'); const filename = generateFilename('textareaName', '.txt'); const textBlob = new Blob([textarea.value], {type: "text/plain;charset=utf-8"}); saveAs(textBlob, filename); });
前面的都相對簡單一些,可是其實除了下載圖片,可能平時也沒什麼業務場景須要到。接下來要說的但是全部商務系統幾乎都能遇到的了,那就是 —— 下載報表,也就是Excel文件。這裏面就使用FileSaver配合js-xlsx來進行excel的純前端下載工做~
下載下來的文件長這樣:
// 下載excel文件 const excelDownloadDom = document.getElementById('download-excel'); excelDownloadDom.addEventListener('click', () => { // 找到table節點調用方法轉化數據 const wb = XLSX.utils.table_to_book(document.querySelector('#table-excel')); // 生成excel數據 const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'array' }); try { // 下載excel文件 saveAs(new Blob([wbout], { type: 'application/octet-stream' }), 'table-excel.xlsx'); } catch (e) { if (typeof console !== 'undefined') console.log(e, wbout) } });
這裏面我只是介紹如何用FileSaver在前端下載excel文件,至於js-xlsx
如何將數據轉化成excel的這裏不作介紹。我只是簡單的調用了js-xlsx
的將table轉成excel的方法,js-xlsx
還有不少高級功能,有這方面需求的去看看官方文檔 js-xlsx就行了~
帶後端支持的下載就要輕鬆不少了,爲何呢,由於上面全部純前端下載均可以與後端進行配合使用,也就是後端生成對應的下載連接下載數據返回給前端,前端根據設計方案按需使用上面幾種方式下載,確定能下載成功。
那麼node端配合下載確定是要下載點不同的東西了——那就是文件流。
有不少場景,那就是大文件不是存在於CDN,而是以文件流的形式存放在內存。那麼就沒有對應的下載連接,下載對應文件的時候,後端返回的就是文件流。而node裏爲咱們提供
Stream
支持各類流操縱。因此咱們能夠在node端直接進行文件的下載。
下載下來的Excel文件:
// 第一步:構造數據 const data = [ [1, 2, 3], [true, false, null, 'sheetjs'], ['foo', 'bar', new Date('2014-02-19T14:30Z'), '0.3'], ['baz', null, 'qux'], ]; // 第二步:生成excel的Buffer數據 const buffer = xlsx.build([{ name: 'mySheetName', data }]); // 第三步:寫文件到本地 const tmpExcel = `filename.xlsx`; fs.writeFileSync( tmpExcel, buffer, { encoding: 'utf8', }, err => { if (err) throw new Error(err); }, ); // 第四步:從本地讀取文件下載到瀏覽器 res.setHeader('Content-disposition', `attachment; filename="${tmpExcel}"`); res.setHeader('Content-Type', 'application/octet-stream'); // pipe generated file to the response fs.createReadStream(tmpExcel).pipe(res); // 下載完成後刪除文件 fs.unlinkSync(tmpExcel);
下載excel在node端我使用的不是
js-xlsx
而是
node-xlsx,由於它構造數據很是簡單,功能也很強大,十分推薦你們使用~
這裏場景不是很容易描述,由於Demo我都是將文件放到本地目錄的,因此我讀取本地文件再下載到本地再下載到瀏覽器,我這不是有病嗎。。。通常場景是文件以文件流的形式存在內存裏,而後咱們經過接口下載到本地再從本地下載到瀏覽器。或者是上傳文件保存到本地,而後在從本地進行相關操做,這裏就不寫示例代碼了。
node端,我使用的是express框架(其餘的框架也都同樣),若是你是文件流直接過來的,那麼直接調用res.attachment()
下載文件流,若是是文件path,那麼能夠直接res.download(filepath)
。具體見demo
上面過程其實多經歷了一步,爲何呢?由於拿到buffer以後咱們其實就能夠直接將buffer流向瀏覽器下載了~這裏我用的是Express框架,直接使用res.attachment()
方法就能夠了。
下載下來的文件與上面如出一轍我就不展現了。
按照個人理解,第二種明顯要比第一種好不少爲何還要列出第一種呢?我我的以爲,第一種雖然必定會犧牲必定的性能,可是先下載到本地就能夠對文件進行一些校驗,好比文件是否完整,文件名之類的是否合法,還有些時候的場景可能。畢竟不是全部的下載場景都像Demo這樣簡單。存在即合理,因此仍是都羅列出來。
// 第一步:構造數據 const data = [ [1, 2, 3], [true, false, null, 'sheetjs'], ['foo', 'bar', new Date('2014-02-19T14:30Z'), '0.3'], ['baz', null, 'qux'], ]; // 第二步:生成buffer const buffer = xlsx.build([{ name: 'mySheetName', data }]); // 第三步:直接下載 res.status(200) .attachment('bufferExcel.xlsx') .send(buffer);
// 上面下載代碼等同於下面這段代碼(nodejs原生代碼) res.setHeader('Content-disposition', `attachment; filename="${tmpExcel}"`); res.setHeader('Content-Type', 'application/octet-stream'); res.end(buffer);
這裏我把文件安裝包放在本地了,而後我先讀取文件內容同時下載到瀏覽器~
// 第一種,已知文件路徑直接下載 try { const packagePath = 'static/download/iTerm2-3_2_5.zip'; res.download(path.join(rootDir, packagePath)); } catch (e) { console.error(e); }
// 第二種,讀取本地文件流向瀏覽器 res.setHeader('Content-disposition', `attachment; filename="download-package.zip"`); res.setHeader('Content-Type', 'application/octet-stream'); fs.createReadStream(path.join(rootDir, packagePath), 'utf-8').pipe(res);
最後給你們安利一個將Stream API使用到極致的Http(Https)請求庫 —— request。
// 不加這一行下載下來的文件沒有後綴 res.setHeader('Content-disposition', 'attachment; filename=node-v8.14.0-linux-x64.tar.gz'); request('https://npm.taobao.org/mirrors/node/v8.14.0/node-v8.14.0-linux-x64.tar.gz') .pipe(res);