1. 程式人生 > 其它 >網頁中直接下載 PDF 檔案而不開啟新的頁面載入 PDF 檔案

網頁中直接下載 PDF 檔案而不開啟新的頁面載入 PDF 檔案

我們知道 <a> 元素有 download 屬性,表示當前連結不是用來瀏覽的,而是用來下載的。它的值是一個字串,表示使用者下載得到的檔名。可是對於 PDF 檔案,瀏覽器預設開啟一個新的頁面載入 PDF 檔案,而不會直接下載該檔案。

這時候我們需要將原來的用於下載的 url 進行轉換,轉換成一個 Blob 物件的 URL

一、用 Blob 物件來讀寫 PDF 檔案

引入 Blob 物件,用 BlobBinary Large Object 二進位制大型檔案) 物件來讀寫 PDF 檔案。

瀏覽器原生提供 Blob() 建構函式,用來生成例項物件。

new Blob(array [, options])

Blob 建構函式接受兩個引數。第一個引數是陣列,成員是字串或二進位制物件,表示新生成的 Blob 例項物件的內容;第二個引數是可選的,是一個配置物件,目前只有一個屬性 type,它的值是一個字串,表示資料的 MIME 型別,預設是空字串。

let htmlFragment = ['<a id="a"><b id="b">hey!</b></a>'];
let myBlob = new Blob(htmlFragment, { type: 'text/html' });

上面程式碼中,例項物件 myBlob 包含的是字串。生成例項的時候,資料型別指定為 text/html

下面是另一個例子,Blob 儲存 JSON 資料。

let obj = { hello: 'world' };
let blob = new Blob([JSON.stringify(obj)], { type: 'application/json' });

blob; // Blob{size: 17, type: "application/json"}

對於 MIME 型別,需要選擇適合二進位制檔案流的型別,而不是普通的文字型別:(MIME 型別)

瀏覽器通常使用 MIME 型別(而不是副檔名)來確定如何處理 URL,因此 Web 伺服器在響應頭中新增正確的 MIME 型別非常重要。如果配置不正確,瀏覽器可能會曲解檔案內容,網站將無法正常工作,並且下載的檔案也會被錯誤處理。

還有一種很重要的 MIME 型別 application/octet-stream

這是應用程式檔案的預設值。意思是 未知的應用程式檔案 ,瀏覽器一般不會自動執行或詢問執行。瀏覽器會像對待 設定了 HTTPContent-Disposition 值為 attachment 的檔案一樣來對待這類檔案。

二、通過 URL.createObjectURL() 方法將 Blob 物件轉換成 url

URL.createObjectURL() 方法將流媒體檔案生成一個 URL 字串。這個字串代表了 Blob 物件的 URL

該方法生成的 URL 就像下面的樣子(以 blob: 開頭的字串)。

blob:http://localhost:3209/28c3a781-3492-4cff-82cc-2f72a0a7f245

至此我們就拿到了 Blob 物件的 URL


當我們拿到下載 PDF 檔案的地址(url)和 PDF 檔名(name)後,先轉換成二進位制檔案流的 url,然後就可以下載該檔案了。

handlePdfLink(url: string, name: string): void {
  fetch(url, {
    method: 'get'
  })
    .then(function (res) {
      if (res.status !== 200) {
        return res.json()
      }
      return res.arrayBuffer()
    })
    .then((blobRes) => {
      // 生成 Blob 物件,設定 type 等資訊
      const e = new Blob([blobRes], {
        type: 'application/octet-stream'
      })
      // 將 Blob 物件轉為 url
      this.blobLink = window.URL.createObjectURL(e)

      this.downloadFile(this.blobLink, name)
    }).catch(err => {
      console.error(err)
    })
}

上面程式碼中 arrayBuffer() 將產生一段二進位制資料。Response.arrayBuffer()

arrayBuffer() 接受一個 Response 流, 並等待其讀取完成. 它返回一個 promise 例項, 並 resolve 一個 ArrayBuffer 物件。

然後將檔案的MIME 型別設定為 application/octet-stream 讓瀏覽器不會自動執行或詢問執行,裝入 Blob 物件中進行讀取檔案的資料內容。

再使用 URL.createObjectURL() 方法,針對 Blob 物件生成一個臨時 URL,以便於某些 API 使用。這個 URLblob: 開頭,表明對應一個 Blob 物件,協議頭後面是一個識別符,用來唯一對應記憶體裡面的 Blob 物件。這一點與 data://URLURL 包含實際資料)和 file://URL(本地檔案系統裡面的檔案)都不一樣。

最後我們新增 <a> 元素給它裝入下載地址 url 和下載檔名 name 來下載 PDF 檔案。

downloadFile(url: string, name: string): void {
  if (url && url.trim()) {
    let a = document.createElement('a');
    a.href = url;
    a.download = name || '未命名檔案';
    a.click();
    window.URL.revokeObjectURL(this.blobLink);
  } else {
    this.msg.remove();
    this.msg.error('檔案下載路徑不能為空!');
  }
}

由於每次使用 URL.createObjectURL() 方法,都會在記憶體裡面生成一個 URL 例項。如果不再需要該方法生成的 URL 字串,為了節省記憶體,下載完成後可以使用 URL.revokeObjectURL() 方法釋放這個例項。

歡迎寫出你的看法,一起成長!