這應該是你見過的最全前端下載總結
自己整理的一些專案中遇到過的關於上傳和下載的一些Demo,大前端系列(也就是純前端 + node端完成的下載,只要獲取到資料下載工作全是前端來做),僅供給位看官參考,避免踩坑,即插即用,歡迎fork和star:star2:,為這個倉庫添磚加瓦~(P.S. 個人認為如果沒寫過上傳下載其實還是挺麻煩的,這個基本能覆蓋大部分場景了~)
包括內容
- 純前端下載
- 基於a標籤
- location && iframe
- FileSaver ---> [推薦]
- node端下載
- 先下載到本地,再下載到瀏覽器
- 直接流向瀏覽器下載 [推薦]
=======> ofollow,noindex">frontend-download-sample
在這裡怕大家沒有耐心看下去,放一個強烈推薦的filesaver的Demo動態圖

寫在前面
上傳和下載個人認為在前端開發時稍微複雜一丟丟,需要額外處理一些事情而不是直接獲取資料渲染頁面,所以想著把平時遇到過的一些場景整理一下分享出來,大牛繞過,不喜勿噴~我平時在專案中接觸的也就是一些上傳圖片,上傳安裝包,下載圖片,下載安裝包以及整理資料生成excel檔案下載下來。暫時還沒有接觸過其他型別的,所以本專案可能有一定的侷限性,只是給大家提供一種思路或者方案,有其他想法歡迎評論~
純前端下載形式
顧名思義,純前端實現,也就是不依賴於任何後端。不過這種方式有一定的侷限性,比如下載型別,寫法,資料形式等等。但是既然不依賴與後端,在可接受範圍之內還是很推薦使用的,畢竟簡單啊~
基於a標籤下載
說到簡單,那麼最簡單的就是這個了。那就是基於 <a>
標籤的下載檔案方式,真的是超級簡單。使用方法如下:
href: 檔案的絕對/相對地址 download: 檔名(可省略,省略後瀏覽器自動識別原始檔名) <a href='xxx.jpg' download='file.jpg'>下載jpg圖片</a> 複製程式碼
那麼既然這麼簡單,那肯定是存在問題的。

上面這張圖片是官方提供的相容性,目前只有FireFox和Chrome支援download屬性。至少這兩個對於開發者來說不陌生,佔有量也很大,所以也還可以吧,但是接下來我又嘗試了一下這兩個瀏覽器的相容性情況。

上面這張,是FireFox瀏覽器最新版,可以看到點選下載檔案會彈出一個對話方塊,之後點選儲存檔案才可以進行下載,同時只能下載不能被瀏覽器開啟的檔案型別,如圖片、文字檔案、html檔案這種可以被開啟的檔案,是無法被下載的直接在瀏覽器進行預覽。

上面這張,是Chrome最新版,與FireFox相同,對於圖片檔案和文字檔案這種可以被瀏覽器開啟的檔案不會被下載,而excel和安裝包這種檔案是可以被直接下載的,無需進行任何二次確認操作。
那麼能不能不讓瀏覽器預覽圖片(或pdf或txt檔案)?
肯定能啊~為什麼呢?其實a標籤的href屬性還可以接受除了相對和絕對路徑之外的其他形式Url,比如下面我們要用到的DataUrl和BlobUrl。我們使用這種形式,就可以讓瀏覽器不預覽而直接下載圖片了,當然了操作起來更麻煩一些了就。
- DataUrl
// 首先,圖片轉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的一些問題以及解決辦法 ,我就不多說了,大家自己去看看。

- BlobUrl 整體邏輯更復雜了,首先 檔案 -> 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來下載就行了。
location.href 和 iframe下載
上面這兩種非常好理解,就是在另一個視窗或者當前位址列地址指向下載連結,下載連結要求是dataUrl或者blobUrl。只不過,iframe是更高階一些,也就是可以幫助我們做到無閃下載,作為開發者大家應該都懂,我就不多BB了。

<a>
標籤下載,為什麼這麼說呢,會因為a標籤方法雖然會預覽瀏覽器可以預覽的檔案,但是如果進行適當轉化,還是能進行下載的。但是location這種方法無論是dataUrl還是blobUrl,只要是圖片、文字檔案以及pdf等所有瀏覽器可以開啟的檔案,都會直接給你預覽,只能下載那些瀏覽器不支援預覽的那些檔案。所以Just so so了。
iframe封裝無閃現下載方法
本質很簡單,就是不讓當前瀏覽器視窗執行下載操作,而是另開一個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 強大的前端下載外掛 -> 【強烈推薦】
FileSaver的下載方式完全是前端(Client-Side)的下載方式,它是基於Blob進行下載的,當然因為是基於前端下載,所以瀏覽器下載會有一定的限制,也就是Blob資料的大小不能過大,看看官網給的相關引數:

可以看到,基本對於支援的瀏覽器來說,大小可以達到 500MB+,應該已經可以滿足大部分需求了。如果檔案確實很大,官網給出了替代方案 StreamSaver ,沒去研究過這個,不過作者既然推薦可能也很好,感興趣的可以去看看。
前面講到了,FileSaver是基於Blob的,其實並不準確,可以看一下官網:

<a>
標籤也挺好的是不?然後基於File一般都是特定場景,比如上傳的時候,才會用到FileReader之類的API,說實話我也沒怎麼用過,都是封裝的,所以這裡也不做介紹。
開頭也說過了,希望小夥伴可以給這個倉庫新增東西啊,可以增加自己的下載Demo到這裡,非常歡迎
所以我這篇文章討論的下載,就是基於Blob。首要工作就是將檔案轉換成Blob資料。下面幾個例子都是這樣:
FileSaver ---- 下載canvas
這個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' ); } }); 複製程式碼
程式碼非常簡單,感興趣的小夥伴可以去看看每個外掛內部的程式碼。我這裡就是應用級別的示例了。
FileSaver ---- 直接下載圖片
直接下載圖片就是將圖片轉換成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。
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); }); 複製程式碼
FileSaver ---- 下載Excel檔案(搭配js-xlsx)
前面的都相對簡單一些,但是其實除了下載圖片,可能平時也沒什麼業務場景需要到。接下來要說的可是所有商務系統幾乎都能遇到的了,那就是 —— 下載報表,也就是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端配合下載(大前端)
帶後端支援的下載就要輕鬆很多了,為什麼呢,因為上面所有純前端下載都可以與後端進行配合使用,也就是後端生成對應的下載連結下載資料返回給前端,前端根據設計方案按需使用上面幾種方式下載,肯定能下載成功。
那麼node端配合下載肯定是要下載點不一樣的東西了——那就是 檔案流 。
有很多場景,那就是大檔案不是存在於CDN,而是以檔案流的形式存放在記憶體。那麼就沒有對應的下載連結,下載對應檔案的時候,後端返回的就是檔案流。而node裡為我們提供 Stream
支援各種流操縱。所以我們可以在node端直接進行檔案的下載。
先下載到本地在從瀏覽器下載
- fs下載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 ,因為它構造資料非常簡單,功能也很強大,十分推薦大家使用~
- fs下載檔案
這裡場景不是很容易描述,因為Demo我都是將檔案放到本地目錄的,所以我讀取本地檔案再下載到本地再下載到瀏覽器,我這不是有病嗎。。。一般場景是檔案以檔案流的形式存在記憶體裡,然後我們通過介面下載到本地再從本地下載到瀏覽器。或者是上傳檔案儲存到本地,然後在從本地進行相關操作,這裡就不寫示例程式碼了。
node端直接流向瀏覽器下載【推薦】
node端,我使用的是express框架(其他的框架也都一樣),如果你是檔案流直接過來的,那麼直接呼叫 res.attachment()
下載檔案流,如果是檔案path,那麼可以直接 res.download(filepath)
。具體見demo
- 直接下載Excel
上面過程其實多經歷了一步,為什麼呢?因為拿到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); 複製程式碼
npmjs.com%2Fpackage%2Frequest" rel="nofollow,noindex">request
最後給大家安利一個將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); 複製程式碼
這裡我為了省時間,就用了自己以前搭過的一個腳手架 Next-Antd-Scafflod ,直接在這裡寫的Demo。你可以理解我在打廣告,你點進去給個star我也不介意:smile:。