隨著前端技術的發展,越來越多的業務場景需要前端來處理文件下載。在眾多的方法中,透過<a>
標籤的download 屬性實作下載是其中常見也是比較簡單的一種方法。
常規的<a>
標籤透過href 實作連結跳轉,如果只想下載檔案而不是跳轉預覽,最好的方式是在<a>
標籤中加入download
屬性,就能很簡單地實作下載操作。
download
是HTML5 中<a>
標籤新增的屬性,此屬性會強制觸發下載操作,指示瀏覽器下載URL 而不是導航到它,並提示使用者將其儲存為本機文件,例如:
<a href=result.png download>download</a>
如果缺少download
屬性,點擊download 會直接變成預覽圖片,當新增download
屬性後則會觸發圖片的下載。
目前download
屬性的相容性如caniuse 中所展示的:
可以看到,大部分主流的瀏覽器基本上都已經支援download
屬性,而IE 的表現一如既往的感人,目前許多Window 系統仍然在使用IE ,這也是許多業務需求需要考慮的。這種相容性問題限制了download
的更廣泛應用。
面對一些動態內容下載的業務場景,即圖片等資源的位址並不是固定的(例如一些線上繪圖工具所產生的圖片),只使用HTML 無法滿足需求。為了能夠滿足不同的URL 下載,可以透過JS 實作一個動態觸發URL 下載的方法。
function download(href, filename='') { const a = document.createElement('a') a.download = filename a.href = href document.body.appendChild(a) a.click() a.remove() }
需要注意的是,程式碼中對創建的<a>
進行的appendChild
和remove
操作主要是為了兼容FireFox 瀏覽器,在FireFox 瀏覽器下調用該方法如果不將創建的<a>
標籤添加到body 裡,點擊連結不會有任何反應,無法觸發下載,而在Chrome 瀏覽器中則不受此影響。
上述的方法可以實現同源資源的下載。但在很多場景中,還需要處理跨域資源。遺憾的是, download
屬性目前僅適用於同源URL ,也就是如果需要下載的資源位址是跨網域的, download
屬性就會失效,點擊連結會變成導覽預覽。
測試:點擊下載,結果只是預覽而無法下載圖片。
註: Chrome65 之前是支援download
屬性觸發檔案跨網域下載的,之後則嚴格遵循同源策略,無法再透過download
屬性觸發跨網域資源的下載。而FireFox 一直不支援跨網域資源的download
屬性下載。
download
屬性不但可以觸發下載,也能指定下載檔名:
<a href=test.png download=joker.png>下載</a>
如果下載檔案的後綴與原始檔案保持一致,可以設定預設檔名:
<a href=test.png download=joker>下載</a>
筆者曾經遇到一個問題,透過<a>
標籤觸發跨域資源下載,程式碼與上述的download方法基本上相同,只是在設定download
屬性的地方有點不同:
a.setAttribute(download, true)
結果在舊版的Chrome 瀏覽器中出現瞭如下情況。
下載檔名成了true
。很明顯,瀏覽器將download
屬性值讀成了檔案名稱。
經過分析,出現上述問題主要是因為:
1. 首先不該將download
設為true
, download
與disabled
這種類型的屬性值不同,它與檔名直接相關聯。而且對於這種前後端響應式下載的方式, download
屬性並不是必要的。
2. 在Chrome 的早期版本不僅支援跨域資源的download
屬性下載,而且還可以透過download
重置跨域資源的檔名,因此才會出現上述這種情況。
前後端配合完成檔案下載的業務場景,一般是由後端設定回應頭中的Content-Disposition
資訊來實現。
在HTTP 場景中,Content-Disposition 第一個參數或是inline(預設值,表示回覆中的訊息體會以頁面的一部分或整個頁面的形式展示),或是attachment(表示訊息體應該下載到本地;大多數瀏覽器會呈現一個儲存為的對話框,將filename 的值預先填入下載後的檔案名稱)。
如果在回應頭中設定了Content-Disposition
,前端也在對應連結的<a>
標籤中加入了download
屬性,那麼此時命名規則:
如果HTTP 頭中的Content-Disposition 屬性賦予了一個不同於此屬性的檔名,HTTP 頭屬性優先於此屬性。
經過測試發現,當HTTP 頭中Content-Disposition
不為空時:
在Chrome 瀏覽器中,不管HTTP 頭中Content-Disposition
的第一個參數是設為attachment還是inline ,只要設定了filename, download
就無法重置檔案名稱。相反,當filename 為空時, download
屬性值會設為檔案名稱。 在FireFox 瀏覽器中,瀏覽器只會讀取Content-Disposition
的filename 值,若是filename 為空,則取來源檔案名稱。此時download
無論如何都無法重設檔名。
總結一下: 未在回應頭設定Content-Disposition
資訊(例如一般直接定位資源的同源URL), download
屬性可以重設檔案名稱。若後端在Content-Disposition
欄位中已經設定了filename,以filename 值為準。
對於後端已經設定了檔案名稱的情況下,如果仍然想要對檔案名稱進行重置,該如何處理呢?
Blob: URL關於download
屬性還有介紹:
儘管HTTP URL 需要位於相同來源中,但可以使用blob: URL 和data: URL ,以方便使用者下載使用JavaScript 產生的內容(例如使用線上繪圖Web 應用程式建立的照片)。
Blob
(Binary Large Object)即二進位大對象,這個我們並不陌生,一些資料庫將Blob用來表示儲存二進位檔案的欄位類型。 File 介面也是基於Blob,繼承了Blob 的功能並將其擴展使其支援使用者係統上的檔案。 Blob 物件透過Blob 建構函式來建立:
Blob(blobParts[, options])
var debug = {hello: world};var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'});
如果需要實作一些簡單的文字或JS 字串之類的檔案下載,可以透過將文字資訊轉換成blob 二進位串流,產生一個Blob URL,配合download
屬性完成下載,程式碼如下。
const downloadText = (text, filename = '') { const a = document.createElement('a') a.download = filename const blob = new Blob([text], {type: 'text/plain'}) // text指需要下載的文字或字串內容a.href = window.URL.createObjectURL(blob) //會產生一個類似blob:http://localhost:8080/d3958f5c-0777-0845-9dcf-2cb28783acaf 這樣的URL字串document.body.appendChild(a) a.click() a.remove()字串document.body.appendChild(a) a.click() a.remove()字串document.body.appendChild(a) a.click()}
這種Blob URL 與常見的HTTP URL 有什麼不同呢?
Blob URL / Object URL是一種偽協議,可讓Blob和File物件用作圖像和二進位資料下載連結等URL來源。
瀏覽器在內部透過URL.createObjectURL()
建立一個對Blob 或File 物件的特殊引用,產生的Blob URL 只能在瀏覽器本地的單一實例和同一會話中使用,而這個URL 物件會在頁面退出的時候被瀏覽器釋放。
因此Blob URL並不能指向一個伺服器資源,你無法在其它頁面中開啟它。同時由於編碼格式有所差別,Blob URL 比起Data URLs 所佔的空間資源更少,效能也更好。
Blob 為Web 開發提供了一個非常有用的功能:建立Blob URL。將二進位資料封裝為Blob 對象,然後使用URL.createObjectURL()
產生Blob URL,由於Blob URL本身就是一個同源URL,可以使用該URL 配合download
解決跨網域資源的下載以及命名問題。
透過Blob 和Fetch 可以解決跨網域和檔案命名的問題:使用fetch
取得跨域資源傳回一個blob 物件並產生一個Blob URL,配合<a>
標籤的download
屬性觸發下載,程式碼如下:
function download(href, filename = '') { const a = document.createElement('a') a.download = filename a.href = href document.body.appendChild(a) a.click() a.remove() }function downloadFile(url, filename='') { fetch(url, { headers: new Headers({ Origin: location.origin, }), mode: 'cors', }) .then(res => res.blob()) .then(blob => { const blobUrl = window.URL.createObjectURL(blob) download(blobUrl, namefile ) window.URL.revokeObjectURL(blobUrl) })}
此時再點擊下載,可以正常的將跨域圖片下載到本地了。
要注意的是跨域資源所在的伺服器需要配置Access-Control-Allow-Origin
訊息,否則會得到一個大寫的跨域報錯。 header 配置例如:
// 允許任何網域存取header('Access-Control-Allow-Origin: *');//指定網域存取header('Access-Control-Allow-Origin: https://h5.ele.me');
目前這種方法還存在一些不足,例如瀏覽器會限制Blob 資料大小不超過500M,在效能方面也會有所不足。
總結目前前端有很多種下載方法, download
屬性下載屬於其中比較簡單的一種,不過仔細考慮其中的一些功能也能挖掘出很多有用的信息。 download
與瀏覽器特性緊密相關,目前該屬性的兼容性也是一大問題,不過連微軟官方都懇求用戶不要再使用IE ,相信以後download
的兼容性問題會持續得到改善,應用前景也會越來越廣闊。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。