1. 程式人生 > 程式設計 >JavaScript實現多檔案下載方法解析

JavaScript實現多檔案下載方法解析

對於檔案的下載,可以說是一個十分常見的話題,前端的很多專案中都會有這樣的需求,比如 highChart 統計圖的匯出,線上圖片編輯中的圖片儲存,線上程式碼編輯的程式碼匯出等等。而很多時候,我們只給了一個連結,使用者需要右鍵點選連結,然後選擇“另存為”,這個過程雖說不麻煩,但還是需要兩步操作,倘若使用者想儲存頁面中的多個連結檔案,就得重複操作很多次,最常見的就是英語聽力網站上的音訊下載,手都要點麻!

本文的目的是介紹如何利用 javascript 進行多檔案的下載,也就是當用戶點選某個連結或者按鈕的時候,同時下載多個檔案。這裡的“同時”用的不是很準確,在現代瀏覽器中可以實現多檔案的並行下載,而在一些老版本瀏覽器,如IE8-,此類的瀏覽器就只能進行單個檔案的下載,但是我們可以讓多個檔案依次儲存下來,算是序列下載吧~

若要要無視實現細節,可以直接跳到第三部分,或者戳:

程式碼封裝:lib.js

DEMO:javascript-multiple-download(HTTPS,第三個有bug,具體原因下面有說明)

javascript-multiple-download(HTTP,測試正常)

一、檔案型別介紹及其特點

1. 一般型別

平時比較常見的有 txt、png、jpg、zip、tar 等各種檔案格式,這些檔案格式中,一部分瀏覽器是會直接開啟連結顯示內容的,而另外一部分,瀏覽器不識別響應頭,或者不能解析對應的格式,於是當做檔案直接下載下來了。如:

<a href="http://barretlee.com/test.rar" rel="external nofollow" >file</a>

這句程式碼,若直接點開連結,瀏覽器將會直接下載該檔案。

2. dataURL型別

dataURL 也是十分常見的型別,他可以作為 src 或者 url() 的引數送進去。比較常見的有如下幾種:

文字: data:text/plain;這裡是正文內容。
圖片: data:image/jpg;base64,/9j/4AAQSkZJRgABAQEA....
  data:image/png;base64,/9j/4AAQSkZJRgABAQEA....

base64 是用的比較廣泛的一種資料格式。

Base64格式
data:[][;charset=][;base64],Base64 在CSS中的使用:
.demoImg{ background-image: url("data:image/jpg;base64,/9j/4QMZRXhpZgAASUkqAAgAAAAL...."); }

Base64 在HTML中的使用:
<img width="40" height="30" src="data:image/jpg;base64,/9j/4QMZRXhpZgAASUkqAAgAAAAL...." />  

3. Blob 流

Blob 物件表示不可變的、包含原始資料的類檔案物件。具體的內容可以參閱MDN文件。

他的使用也是特別的方便,如:

var aFileParts = ['<a id="a"><b id="b">hey!</a>'];
var oMyBlob = new Blob(aFileParts,{type : 'text/html'}); // the blob

Blob 接收兩個引數,一個是陣列型別的資料物件,他可以是 ArrayBuffer、ArrayBufferView、Blob、String 等諸多型別;第二個引數是 MINE 型別設定。而本文我們要用到的是 URLcreateObjectURL() 這個函式,他的作用是將一個 URL 所代表的內容轉化成一個DOMString,產生的結果是一個 檔案物件 或者 Blob 物件。

4. 二進位制流

我們利用 File API 讀取檔案的時候,拿到的是資料的二進位制流格式,這些型別可以直接被 ArrayBuffer 等接收,本文中沒有用到,就不細說了。

二、JavaScript 多檔案下載

HTML5 中 a 標籤多了一個屬性——download,使用者點選連結瀏覽器會開啟並顯示該連結的內容,若在連結中加了 download 屬性,點選該連結不會開啟這個檔案,而是直接下載。雖說是比較好用,但低版本瀏覽器不相容,這個在本節的 2 和 3 中將會講到解決方案。

在這裡,我們可以利用屬性檢測UA 來判斷瀏覽器型別:

h5Down = document.createElement("a").hasOwnProperty("download");
var h5Down = !/Trident|MSIE/.test(navigator.userAgent); // Trident 標識 IE11

1. a 標籤 download 屬性的使用

注:FF5.0 / Safari5.0 / Opera11.1 / IE9.0 不支援 download 屬性

利用 download 屬性可以直接下載單個檔案,若想點選一次下載多個檔案,就得稍加處理下了:

function downloadFile(fileName,content){
 var aLink = document.createElement("a"),evt = document.createEvent("HTMLEvents");

 evt.initEvent("click");
 aLink.download = fileName;
 aLink.href = content;

 aLink.dispatchEvent(evt);
}

download 屬性的作用除了讓瀏覽器忽略檔案的 MIME 型別之外,還會把該屬性的值作為檔名。你可以在 chrome 控制檯執行這句程式:

downloadFile("barretlee.html","./");

瀏覽器會提示是否保留(下載)該 html 檔案。之前我們提到檔案型別還可能是 dataURL 或者是 Blob 流,為了讓程式也支援這些資料型別,稍微修改下上面的函式:

function downloadFile(fileName,content){
 var aLink = document.createElement('a');,blob = new Blob([content]),evt = document.createEvent("HTMLEvents");

 evt.initEvent("click");

 aLink.download = fileName;
 aLink.href = URL.createObjectURL(blob);
 aLink.dispatchEvent(evt);
}

new Blob([content]),現將檔案轉換成一個 Blog 流,然後,使用 URL.createObjectURL() 將其轉換成一個 DOMString。這樣我們就支援 data64 和其他資料型別的 content 了~

2. window.open 之後 execCommand("SaveAs")

上面也提到了,儘管 download 屬性是十分便利的 H5 利器,但低版本 IE 根本不賞臉,要說方法,IE 還是有很多方式去轉換的,比如 ADOBE.STREAM 的 activeX 物件可以把檔案轉換成檔案流,然後寫入到一個要儲存的檔案中。這裡要談到的是略微方便一點的方式:先把內容寫到一個新開的 window 物件中,然後利用 execCommand 執行儲存命令,就相當於我們在頁面上按下 Ctrl+S,這樣頁面內的資訊都會 down 下來。

// 將檔案在一個 window 視窗中開啟,並隱藏這個視窗。
var win = window.open("path/to/file.ext","new Window","width=0,height=0");
// 在 win 視窗中按下 ctrl+s 儲存視窗內容
win.document.execCommand("SaveAs",true,"filename.ext");
// 使用完了,關閉視窗
win.close();

這個過程十分明瞭,不過這裡會存在一個問題,並不是程式的問題,而是瀏覽器的問題,如果我們用 搜狗瀏覽器 或者 360瀏覽器 開啟新視窗的話,他會新開一個標籤頁,而不是新開一個視窗,更可惡的是部分瀏覽器攔截 window.open 的視窗(這個可以設定)。所以只好另覓他法了。

3. iframe 中操作

既然新開一個視窗那麼麻煩,我就在本視窗下完成工作~

function IEdownloadFile(fileName,contentOrPath){
 var ifr = document.createElement('iframe');

 ifr.style.display = 'none';
 ifr.src = contentOrPath;

 document.body.appendChild(ifr);


 // 儲存頁面 -> 儲存檔案
 ifr.contentWindow.document.execCommand('SaveAs',false,fileName);
 document.body.removeChild(ifr);
}

一般的連結我們可以直接給 iframe 新增 src 屬性,然後執行 saveAs 命令,倘若我們使用的是 data64 編碼的檔案,這個怎麼辦?

var isImg = contentOrPath.slice(0,10) === "data:image";

// dataURL 的情況
isImg && ifr.contentWindow.document.write("<img src='" + 
  contentOrPath + "' />");

這個也比較好處理,直接把檔案寫入到 iframe 中,然後在執行儲存。

三、程式碼的封裝與介面介紹

1. 程式碼的封裝以及相關 DEMO

封裝:lib.js

DEMO:javascript-multiple-download(HTTPS,第三個有bug)

javascript-multiple-download(HTTP,測試正常)

Bug 說明,經過一番細節處理之後,基本相容各個瀏覽器,我把程式碼放在 https://raw.github.com 上託管,可能因為是 https 傳輸,第三個測試中報錯了,報錯的具體內容是:HTTPS 安全受到 http://rawgithub.com/barretlee/javascript-multiple-download/master/file/test.jpg 的威脅,而 test.txt 檔案沒有報錯。放到 http 協議下測試執行結果是可觀的。(這點我沒有去深究,肯定是有深層安全方面原因的,難道就因為他是 jpg圖片格式? 謝@屈屈提醒,跨協議傳輸存在安全問題)後面的 demo 我放在 BAE 上,沒有問題,不過沒測試 safari 和 opera。

2. 介面的呼叫

提供了三個介面,支援單檔案下載,多檔案下載,多檔案下載自定義命名。

1)單檔案下載

Downer("./file/test.txt");

2)多檔案下載

Downer(["./file/test.txt","./file/test.txt"]);

3)多檔案下載自定義命名

Downer({
 "1.txt":"./file/test.txt","2.jpg":"./file/test.jpg"
}); 

檔案的 URL 如 ./file/test.jpg 都可以改成 base64 或者其他格式,如:

Downer({
  //這是一個很長的 dataURI,我用負的text-indent隱藏了,可直接複製
 "data64.jpg" : "data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAAYADsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9NgKKK8w8beO/EMVzGdJ08W2jW2t2WnXGpLdx+e7PPEkii3eJgYj5mwsJFkzkquAC3VFc0lFf10/UOlz1CivO9H+LEmreP7jw8mlBrLfPDa6nC1y0cksX30Z2t1hGCHBEcsjAqQVHOJPCfjnxFc+HNU1rxPpmh6Vp9mt232i01WWXmGV0IdXt0CrhD8+45xnaM4A00uZ9rgtZcq3vb5noFLXCfDz4j3vjbT9XM+hSabqdhtItWFxGswZCybTc28DjJDAkx7fRjzi/qmv6zH8LdR1m6sP+Ef12PSprprPzkufssyxMwXeBtfBA5xg0OLi2n/Vwh+8aUep1gFLXGeKhqmj+FtGmt9fvvtVvfWMc9w0VsWvUkuI43WUeVtAIcnMYQggYI6Hs6lqwk7pPuNrkPEHwn8N+KNRkvdQh1BpZJY7ho7fVru3haWPbslMUcqpvXYhD7d2VU54FFFNNxd1uPyJIfhb4dt/EkOuxwXy6hBNJPD/xNLryImkz5myHzPLUNkllC4J5IyAaWP4W+GY7+/uzp7yy3sc8UiTXc0kUazHdMIo2cpDvPLeWFyeTRRRd9wH6B8NNB8NyapJZx38kmpxrFePe6pdXbTKAQMmaRjkAkZHOMDOAKmbwLp9n8Pp/CGkr/Zmm/wBnyadbjLS+QjIUB+ZstjPdsn1oopOTfUa91prdf1+hFrfhnWNb0HSLCTVrGOaC6tbi+mXT323CwyrJtiXzv3RZkXljJgZ4PWuooopNtkpWVkf/2Q==" 
});

這裡只做到了 chrome 相容,IE 下懶得去看了,這個需求很少見!

四、伺服器支援與後端實現

1. 後端實現

不使用前端,直接後端實現的原理,就是在響應頭中加入一些特殊的標記,如前端傳送這樣的請求:

function download(path) {
 var ifrm = document.getElementById(frame);
 ifrm.src = "download.php?path="+path;
}

後端的響應為

<?php 
 header("Content-Type: application/octet-stream");
 header("Content-Disposition: attachment; filename=".$_GET['path']);
 readfile($_GET['path']);
?>

告訴瀏覽器這是一個流檔案,作為附件方式傳送給你,請忽略 MINE type,直接儲存。

2. 伺服器配置

若後臺是 apche 作為伺服器,可以配置 htaccess 檔案:

<filesmatch "\.(zip|rar)$"="">
Header set Content-Disposition attachment
</filesmatch>

意思是隻要請求的是 zip 或者 rar 型別的檔案,那麼就新增一個 Content-Disposition:attachment 的響應頭。這樣就可以在 php 程式碼中省略麻煩的操作。

五、小結

由於行文倉促,文中會有不少錯誤,對多檔案下載有更好的提議,希望提出來共同分享!

實現多檔案下載的方式肯定不止上面提到的幾種,而且我這裡封裝的程式碼並沒有在FF safari opera 中實現,因為他們還沒相容 download 屬性,具體情況可以檢視 caniuse。建議在專案中把這樣的事情交給後端,幾句程式碼可以搞定。

六、參考資料

在瀏覽器端用JS建立和下載檔案 AlloyTeam

Starting file download with Javascript Ahzaz's Blog

Blob 流 MDN

到此這篇關於JavaScript實現多檔案下載方法解析的文章就介紹到這了,更多相關JavaScript多檔案下載內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!