經過 JavaScript 下載文件到本地(單文件)

最近在作一個 文件下載的功能,這裏把作的過程當中用的技術和坑簡要總結下。

1. 單文件下載(a標籤)

同源單文件

針對單文件的狀況下,同源的文件,能夠經過 < a> 標籤的 download 屬性下載文件前端

const elt = document.createElement('a');
  elt.setAttribute('href', url);
  elt.setAttribute('download', 'file.png');
  elt.style.display = 'none';
  document.body.appendChild(elt);
  elt.click();
  document.body.removeChild(elt);

可是這個方案並不適用於非同源的資源,此時它至關於普通的超連接,點擊會跳轉到資源頁面,而不是下載。canvas

非同源圖片

若是不存在CORS問題, 能夠藉助Blob實現下載(構造xhr請求文件地址, 以Blob的形式接收Response):後端

function downloadWithBlob(url) {
  fetch(url).then(res => res.blob().then(blob => {
    var a = document.createElement('a');
    var url = window.URL.createObjectURL(blob);
    var filename = 'file.png';
    a.href = url;
    a.download = filename;
    a.click();
    window.URL.revokeObjectURL(url);
  }));
}

若是存在CORS問題,能夠考慮使用 canvas 將圖片轉換成 base64 編碼以後再經過 標籤的 download 屬性下載瀏覽器

function downloadPic(url) {
  const img = new Image;
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  img.onload = function() {
    canvas.width = this.width;
    canvas.height = this.height;
    ctx.drawImage(this, 0, 0);

    const elt = document.createElement('a');
    elt.setAttribute('href', canvas.toDataURL('image/png'));
    elt.setAttribute('download', 'file.png');
    elt.style.display = 'none';
    document.body.appendChild(elt);
    elt.click();
    document.body.removeChild(elt);
  };
  img.crossOrigin = 'anonymous';
  img.src = url;
}

2. 單文件下載(iframe)

iframe方式是在頁面內隱藏iframe, 而後將下載地址加載到iframe中, 從而觸發瀏覽器的下載行爲app

const iframe = document.createElement('iframe');
  iframe.src = url;
  iframe.style.display = 'none';
  document.body.appendChild(iframe);

可是這裏發現,即便是同域的圖片,也沒法完成下載,這是爲啥呢?fetch

這裏就有個上面的a連接下載沒有提到的問題:什麼樣的連接才能觸發瀏覽器的下載:this

url如何觸發瀏覽器自動下載

一個url可否觸發瀏覽器自動下載,主要看該請求響應頭response header是否知足,通常是看Content-DispositionContent-Type這兩個消息頭:編碼

  • response header中指定了Content-Disposition爲attachment,它表示讓瀏覽器把消息體以附件的形式下載並保存到本地 (通常還會指定filename, 下載的文件名默認就是filename)
  • response header中指定了Content-Type 爲 application/octet-stream(無類型) 或 application/zip(zip包時)等等。(其中 application/octet-stream表示http response爲二進制流(沒指定明確的type), 用在未知的應用程序文件,瀏覽器通常不會自動執行或詢問執行。瀏覽器會像對待 設置了HTTP頭Content-Disposition 值爲 attachment 的文件同樣來對待這類文件)

只要url知足上述觸發的要求,那麼均可以經過iframe的形式來下載url

3. 代理服務處理下載

若是後端本身也能控制的話,或者後端能配合的話,能夠寫一個代理服務,在後端去請求文件數據,而後設置好相應的response header, 而後前端請求代理服務來作下載。代理

前端(假設代理服務接口是http://exampale.com/download)

const downloadUrl = 'http://exampale.com/download?url=' + encodeURIComponent(url) + '&name=xxx';
  const elt = document.createElement('a');
  elt.setAttribute('href', downloadUrl);
  elt.setAttribute('download', 'file.png');
  ...

後端

const url = decodeURIComponent(req.query.url);
http.get(url, (response) => {
  res.setHeader('Content-disposition', 'attachment;filename=' + req.query.name);
  res.setHeader('Content-type', 'application/octet-stream');
  response.pipe(res);
});

單文件的處理先寫到這裏,多文件的下載下篇在寫。

相關文章
相關標籤/搜索