1. 程式人生 > 實用技巧 >js-link下載檔案

js-link下載檔案

1. 需求

JS要實現下載功能,一般都是這麼幾個過程:生成下載的URL,動態建立一個A標籤,並將其href指向生成的URL,然後觸發A標籤的單擊事件,這樣就會彈出下載對話方塊,從而實現了一個下載的功能。

這裡所說的下載,有時候也可以理解為儲存。出於安全考慮,JS肯定無法直接呼叫FileAPI寫檔案到磁碟,但是卻可以通過下載來變相實現儲存功能。

2.1. JS觸發單擊事件

既然是用A標籤模擬,那麼肯定要知道JS如何主動觸發單擊事件。

最簡單的觸發單擊事件肯定是elem.click(),平時在不需要考慮相容性的場合我都是這麼幹的,但是畢竟這個方法有相容性(具體相容性如何沒做過測試),所以還是要掌握一個通用的方法。

以下程式碼是網上比較容易找到的一段程式碼,我在前面加了一段MouseEvent的判斷:

/**
 * 考慮相容性的觸發單擊事件
 * @param elem 要觸發單擊事件的DOM物件
 */
function fireClickEvent(elem)
{
	var event;
	if(window.MouseEvent) event = new MouseEvent('click');
	else
	{
		event = document.createEvent('MouseEvents');
		event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
	}
	elem.dispatchEvent(event);
}

2.2. HTML5的download屬性

這個屬性很重要,它可以指定下載檔名,並且可以告訴瀏覽器目標連結是一個下載連結,不是一個普通連結,我們看下面程式碼就能看出區別了:

<a href="data:text/txt;charset=utf-8,測試下載純文字" download="測試.txt" >下載1</a>
<a href="data:text/txt;charset=utf-8,測試下載純文字">下載2</a>

可以發現,下載1按鈕能夠實現下載,點選下載2連結時直接在瀏覽器開啟檔案內容了。

補充說明:

  • file:///模式下貌似不生效;
  • 連結指向一些第三方連結時也不會生效,具體有待研究;

2.3. JS彈出下載對話方塊

假如給我們的不是一個下載地址而是一個blob物件,我們可以通過URL.createObjectURL來給blob物件生成臨時URL,並且可以利用HTML5的download屬性來指定下載的檔名,好傢伙,有了這2個東西我們就可以實現一個“萬能”的彈出下載對話方塊方法了。

綜上所述,我又在fireClickEvent的基礎上繼續簡單封裝了一個openDownloadDialog方法,使用如下:

  • openDownloadDialog(url, saveName)
  • openDownloadDialog(blob, saveName)

程式碼如下:

/**
 * 通用的開啟下載對話方塊方法,沒有測試過具體相容性
 * @param url 下載地址,也可以是一個blob物件,必選
 * @param saveName 儲存檔名,可選
 */
function openDownloadDialog(url, saveName)
{
	if(typeof url == 'object' && url instanceof Blob)
	{
		url = URL.createObjectURL(url); // 建立blob地址
	}
	var aLink = document.createElement('a');
	aLink.href = url;
	aLink.download = saveName || ''; // HTML5新增的屬性,指定儲存檔名,可以不要字尾,注意,file:///模式下不會生效
	var event;
	if(window.MouseEvent) event = new MouseEvent('click');
	else
	{
		event = document.createEvent('MouseEvents');
		event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
	}
	aLink.dispatchEvent(event);
}

JS實現常見檔案型別的下載

3.1. JS生成CSV檔案並下載

csv是一種逗號分隔的表格檔案格式,可以很好的被Excel支援,由於其檔案格式簡單,所以經常用在簡單的表格上面。最重要的是它是一種純文字格式,可以很輕鬆地用JS來生成而不借助第三方庫。

3.1.1. CSV格式示例

如下:

姓名,期中成績,期末成績
張三,58,95
李四,98,74
王二,47,38
劉能,15,100
黃五,87,68

excel開啟效果如下:

3.1.2. 初次嘗試

首先想到的是使用data:text/txt;來實現,先看一下下載純文字:

<a download="測試.txt" href="data:text/txt;charset=utf-8,測試下載純文字">下載</a>

以上程式碼沒毛病,然後再換成csv。換csv的最大問題就是如何處理換行,很簡單,用encodeURIComponent編碼一下就可以了:

<button onclick="test()">下載CSV</button>
<script>
function test()
{
	var csv = '姓名,期中成績,期末成績\n張三,58,95\n李四,98,74';
	var a = document.createElement('a');
	a.href = 'data:text/txt;charset=utf-8,'+encodeURIComponent(csv);
	a.download = '測試.csv';
	a.click(); // 這裡偷個懶,直接用click模擬
}
</script>

3.1.3. 解決CSV亂碼問題

雖然我們用的是UTF-8編碼,下載後你會發現,用文字編輯器開啟沒問題,但是用Excel開啟亂碼:

別急,原因就是少了一個\ufeffBOM頭,改成這樣就沒問題了:

<button onclick="test()">下載CSV</button>
<script>
function test()
{
	var csv = '姓名,期中成績,期末成績\n張三,58,95\n李四,98,74';
	var a = document.createElement('a');
	a.href = 'data:text/txt;charset=utf-8,\ufeff'+encodeURIComponent(csv);
	a.download = '測試.csv';
	a.click(); // 這裡偷個懶,直接用click模擬
}
</script>

3.1.4. 繼續解決下載檔名的問題

大部分瀏覽器可能都沒啥問題,但是一些比較老的Chrome可能下載的時候指定的download就是不生效,此時可以用blob來解決:

var csv = '姓名,期中成績,期末成績\n張三,58,95\n李四,98,74';
var blob = new Blob(['\ufeff' + data], {type: 'text/csv,charset=UTF-8'});
openDownloadDialog(blob, '測試.csv');

建議一般情況下都用這種方法,穩妥一點。

3.1.5. 最後總結

不考慮相容性的儲存CSV方法:

/**
 * 儲存CSV檔案
 * @params csv csv檔案內容
 * @params saveName 儲存的檔名
 */
function saveCSV(csv, saveName)
{
	var a = document.createElement('a');
	a.href = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(csv);
	a.download = saveName;
	a.click();
}

考慮相容性的儲存CSV方法:

/**
 * 儲存CSV檔案
 * @params csv csv檔案內容
 * @params saveName 儲存的檔名
 */
function saveCSV(csv, saveName)
{
	var blob = new Blob(['\ufeff' + csv], {type: 'text/csv,charset=UTF-8'});
	openDownloadDialog(blob, saveName);
}

3.1.6. 20180115更新

今天碰到了一個CSV檔案太長導致下載失敗的問題(有1.6MB),如下圖:

經測試發現,直接採用拼接字串的方法對於太大的csv檔案可能會下載失敗,換成blob物件就不會,所以,建議無論何時都採用blob方法最穩妥。

3.2. JS實現純文字的下載儲存

掌握了csv,再去下載純文字基本上就沒啥問題了,就是換一下檔案型別而已:

var txt = '你好,我是小茗同學!\n測試換行!';
var blob = new Blob([txt], {type: 'text/txt,charset=UTF-8'});
openDownloadDialog(blob, '測試.csv');

3.3. JS實現圖片的下載儲存

網頁上一般要儲存圖片都是從canvas裡面拿到的圖片資料,通過toDataURL轉換為base64資料:

/**
 * 將某個canvas儲存為圖片
 * @param canvasObj canvas物件
 * @param saveName 儲存的名稱
 * @param type 儲存的圖片格式,如 image/png
 * @param quality 圖片質量,可選0-1
 */
function saveImage(canvasObj, saveName, type, quality)
{
	if(!canvasObj) return;
	type = type || 'image/png';
	quality = quality || 0.92;
	var url = canvasObj.toDataURL(type, quality).replace(/image\/.*?;/, 'image/octet-stream;');
	openDownloadDialog(url, saveName);
}