JavaScript實現(xiàn)多文件下載方法解析
對于文件的下載,可以說是一個十分常見的話題,前端的很多項目中都會有這樣的需求,比如 highChart 統(tǒng)計圖的導出,在線圖片編輯中的圖片保存,在線代碼編輯的代碼導出等等。而很多時候,我們只給了一個鏈接,用戶需要右鍵點擊鏈接,然后選擇“另存為”,這個過程雖說不麻煩,但還是需要兩步操作,倘若用戶想保存頁面中的多個鏈接文件,就得重復操作很多次,最常見的就是英語聽力網(wǎng)站上的音頻下載,手都要點麻!
本文的目的是介紹如何利用 javascript 進行多文件的下載,也就是當用戶點擊某個鏈接或者按鈕的時候,同時下載多個文件。這里的“同時”用的不是很準確,在現(xiàn)代瀏覽器中可以實現(xiàn)多文件的并行下載,而在一些老版本瀏覽器,如IE8-,此類的瀏覽器就只能進行單個文件的下載,但是我們可以讓多個文件依次保存下來,算是串行下載吧~
若要要無視實現(xiàn)細節(jié),可以直接跳到第三部分,或者戳:
代碼封裝:lib.js
DEMO:javascript-multiple-download(HTTPS,第三個有bug,具體原因下面有說明)
javascript-multiple-download(HTTP,測試正常)
一、文件類型介紹及其特點
1. 一般類型
平時比較常見的有 txt、png、jpg、zip、tar 等各種文件格式,這些文件格式中,一部分瀏覽器是會直接打開鏈接顯示內(nèi)容的,而另外一部分,瀏覽器不識別響應頭,或者不能解析對應的格式,于是當做文件直接下載下來了。如:
<a rel="external nofollow" >file</a>
這句代碼,若直接點開鏈接,瀏覽器將會直接下載該文件。
2. dataURL類型
dataURL 也是十分常見的類型,他可以作為 src 或者 url() 的參數(shù)送進去。比較常見的有如下幾種:
文本: data:text/plain;這里是正文內(nèi)容。 圖片: data:image/jpg;base64,/9j/4AAQSkZJRgABAQEA.... data:image/png;base64,/9j/4AAQSkZJRgABAQEA....
base64 是用的比較廣泛的一種數(shù)據(jù)格式。
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 對象表示不可變的、包含原始數(shù)據(jù)的類文件對象。具體的內(nèi)容可以參閱MDN文檔。
他的使用也是特別的方便,如:
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; var oMyBlob = new Blob(aFileParts, {type : 'text/html'}); // the blob
Blob 接收兩個參數(shù),一個是數(shù)組類型的數(shù)據(jù)對象,他可以是 ArrayBuffer、ArrayBufferView、Blob、String 等諸多類型;第二個參數(shù)是 MINE 類型設置。而本文我們要用到的是 URLcreateObjectURL() 這個函數(shù),他的作用是將一個 URL 所代表的內(nèi)容轉(zhuǎn)化成一個DOMString,產(chǎn)生的結(jié)果是一個 文件對象 或者 Blob 對象。
4. 二進制流
我們利用 File API 讀取文件的時候,拿到的是數(shù)據(jù)的二進制流格式,這些類型可以直接被 ArrayBuffer 等接收,本文中沒有用到,就不細說了。
二、JavaScript 多文件下載
HTML5 中 a 標簽多了一個屬性——download,用戶點擊鏈接瀏覽器會打開并顯示該鏈接的內(nèi)容,若在鏈接中加了 download 屬性,點擊該鏈接不會打開這個文件,而是直接下載。雖說是比較好用,但低版本瀏覽器不兼容,這個在本節(jié)的 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 流,為了讓程序也支持這些數(shù)據(jù)類型,稍微修改下上面的函數(shù):
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]),現(xiàn)將文件轉(zhuǎn)換成一個 Blog 流,然后,使用 URL.createObjectURL() 將其轉(zhuǎn)換成一個 DOMString。這樣我們就支持 data64 和其他數(shù)據(jù)類型的 content 了~
2. window.open 之后 execCommand("SaveAs")
上面也提到了,盡管 download 屬性是十分便利的 H5 利器,但低版本 IE 根本不賞臉,要說方法,IE 還是有很多方式去轉(zhuǎn)換的,比如 ADOBE.STREAM 的 activeX 對象可以把文件轉(zhuǎn)換成文件流,然后寫入到一個要保存的文件中。這里要談到的是略微方便一點的方式:先把內(nèi)容寫到一個新開的 window 對象中,然后利用 execCommand 執(zhí)行保存命令,就相當于我們在頁面上按下 Ctrl+S,這樣頁面內(nèi)的信息都會 down 下來。
// 將文件在一個 window 窗口中打開,并隱藏這個窗口。 var win = window.open("path/to/file.ext", "new Window", "width=0,height=0"); // 在 win 窗口中按下 ctrl+s 保存窗口內(nèi)容 win.document.execCommand("SaveAs", true, "filename.ext"); // 使用完了,關(guān)閉窗口 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 屬性,然后執(zhí)行 saveAs 命令,倘若我們使用的是 data64 編碼的文件,這個怎么辦?
var isImg = contentOrPath.slice(0, 10) === "data:image"; // dataURL 的情況 isImg && ifr.contentWindow.document.write("<img src='" + contentOrPath + "' />");
這個也比較好處理,直接把文件寫入到 iframe 中,然后在執(zhí)行保存。
三、代碼的封裝與接口介紹
1. 代碼的封裝以及相關(guān) DEMO
封裝:lib.js
DEMO:javascript-multiple-download(HTTPS,第三個有bug)
javascript-multiple-download(HTTP,測試正常)
Bug 說明,經(jīng)過一番細節(jié)處理之后,基本兼容各個瀏覽器,我把代碼放在 https://raw.github.com 上托管,可能因為是 https 傳輸,第三個測試中報錯了,報錯的具體內(nèi)容是:HTTPS 安全受到 http://rawgithub.com/barretlee/javascript-multiple-download/master/file/test.jpg 的威脅,而 test.txt 文件沒有報錯。放到 http 協(xié)議下測試運行結(jié)果是可觀的。(這點我沒有去深究,肯定是有深層安全方面原因的,難道就因為他是 jpg圖片格式? 謝@屈屈提醒,跨協(xié)議傳輸存在安全問題)后面的 demo 我放在 BAE 上,沒有問題,不過沒測試 safari 和 opera。
2. 接口的調(diào)用
提供了三個接口,支持單文件下載,多文件下載,多文件下載自定義命名。
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 下懶得去看了,這個需求很少見!
四、服務器支持與后端實現(xiàn)
1. 后端實現(xiàn)
不使用前端,直接后端實現(xiàn)的原理,就是在響應頭中加入一些特殊的標記,如前端發(fā)送這樣的請求:
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']); ?>
告訴瀏覽器這是一個流文件,作為附件方式發(fā)送給你,請忽略 MINE type,直接保存。
2. 服務器配置
若后臺是 apche 作為服務器,可以配置 htaccess 文件:
<filesmatch "\.(zip|rar)$"=""> Header set Content-Disposition attachment </filesmatch>
意思是只要請求的是 zip 或者 rar 類型的文件,那么就添加一個 Content-Disposition:attachment 的響應頭。這樣就可以在 php 代碼中省略麻煩的操作。
五、小結(jié)
由于行文倉促,文中會有不少錯誤,對多文件下載有更好的提議,希望提出來共同分享!
實現(xiàn)多文件下載的方式肯定不止上面提到的幾種,而且我這里封裝的代碼并沒有在FF safari opera 中實現(xiàn),因為他們還沒兼容 download 屬性,具體情況可以查看 caniuse。建議在項目中把這樣的事情交給后端,幾句代碼可以搞定。
六、參考資料
在瀏覽器端用JS創(chuàng)建和下載文件 AlloyTeam
Starting file download with Javascript Ahzaz's Blog
Blob 流 MDN
到此這篇關(guān)于JavaScript實現(xiàn)多文件下載方法解析的文章就介紹到這了,更多相關(guān)JavaScript多文件下載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ie中js創(chuàng)建checkbox默認選中問題探討
js創(chuàng)建checkbox默認選中在某些特殊情況下還是比較實用的,下面有個不錯的示例,大家可以參考下2013-10-10H5基于iScroll實現(xiàn)下拉刷新和上拉加載更多
這篇文章主要為大家詳細介紹了H5基于iScroll實現(xiàn)下拉刷新和上拉加載更多效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07JavaScript中各種編碼解碼函數(shù)的區(qū)別和注意事項
JavaScript 中encodeURI,encodeURIComponent與escape的區(qū)別和注2010-08-08