如何在内存中创建一个供用户下载的文件,而不通过服务器?

1114
有没有办法在客户端创建一个文本文件,并提示用户下载,而不需要与服务器进行任何交互?
我知道我不能直接写入他们的机器(出于安全考虑),但我能创建文件并提示他们保存吗?

4
截至2014年4月,W3C尚未将文件系统API标准化。我猜那些使用Blob方案的人应该要小心谨慎一些。HTML5 Rocks网站的提示 W3C文件系统API邮件列表 - pravin
7
请参见: JavaScript:创建和保存文件 - Henry Woody
22个回答

1006

适用于支持 HTML5 的浏览器的简单解决方案...

function download(filename, text) {
  var element = document.createElement('a');
  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  element.setAttribute('download', filename);

  element.style.display = 'none';
  document.body.appendChild(element);

  element.click();

  document.body.removeChild(element);
}
form * {
  display: block;
  margin: 10px;
}
<form onsubmit="download(this['name'].value, this['text'].value)">
  <input type="text" name="name" value="test.txt">
  <textarea name="text"></textarea>
  <input type="submit" value="Download">
</form>

使用方法

download('test.txt', 'Hello world!');

13
好的。这正是@MatthewFlaschen在大约3年前在这里发布的内容(https://dev59.com/wHA65IYBdhLWcg3wuhIR#18197341?noredirect=1#answer-3665147)。 - Joseph Silber
66
是的,但是使用 download 属性可以指定文件名;-) - Matěj Pokorný
6
如果您在文件名中没有提供扩展名,Chrome只会将“txt”扩展名附加在后面。如果您执行download("data.json", data),它将按预期工作。 - Carl Smith
9
这在Chrome(73.0.3683.86)和Firefox(66.0.2)中起作用。但在IE11(11.379.17763.0)和Edge(44.17763.1.0)中不起作用 - Sam
6
不需要将元素附加到DOM。 - sean
显示剩余17条评论

486

你可以使用数据URI。 浏览器支持有所不同,请参见维基百科。 例如:

<a href="data:application/octet-stream;charset=utf-16le;base64,//5mAG8AbwAgAGIAYQByAAoA">text file</a>

使用octet-stream可以强制下载提示。否则,文件可能会在浏览器中打开。

对于CSV文件,您可以使用:

<a href="data:application/octet-stream,field1%2Cfield2%0Afoo%2Cbar%0Agoo%2Cgai%0A">CSV Octet</a>

请尝试使用jsFiddle演示


21
这不是跨浏览器的解决方案,但绝对值得一看。例如,IE 仅限于支持数据 URI。IE 8 限制大小为32KB,而 IE 7 及更低版本根本不支持。 - Darin Dimitrov
9
在Chrome版本19.0.1084.46中,该方法会生成以下警告: "资源被解释为文档,但MIME类型为text/csv: "data:text/csv,field1%2Cfield2%0Afoo%2Cbar%0Agoo%2Cgai%0A"."下载不会被触发。 - Chris
3
现在它在Chrome中有效(已经测试过v20和v21),但在IE9中无效(这可能只是jsFiddle的问题,但我有点怀疑)。 - earcam
7
正确的字符集几乎肯定是UTF-16,除非你有代码将其转换为UTF-8。JavaScript在内部使用UTF-16。如果你有一个文本或CSV文件,请以'\ufeff'开头,这是UTF-16BE的字节顺序标记,文本编辑器将能够正确读取非ASCII字符。 - larspars
27
只需添加download="txt.csv"属性,即可获得适当的文件名和扩展名,并告诉您的操作系统如何处理它。 - elshnkhll
显示剩余21条评论

322

一个适用于 IE 10+,Firefox 和 Chrome 的示例(无需使用 jQuery 或其他任何库):

function save(filename, data) {
    const blob = new Blob([data], {type: 'text/csv'});
    if(window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    }
    else{
        const elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;        
        document.body.appendChild(elem);
        elem.click();        
        document.body.removeChild(elem);
    }
}
请注意,根据您的情况,您可能还需要在删除 elem 后调用URL.revokeObjectURL。根据URL.createObjectURL的文档说明:
每次调用 createObjectURL() 都会创建一个新的对象URL,即使您已经为相同的对象创建了一个对象URL。当您不再需要它们时,必须通过调用 URL.revokeObjectURL() 来释放它们中的每一个。浏览器会在文档卸载时自动释放这些资源;但是,为了获得最佳性能和内存使用情况,如果存在安全时间可以明确卸载它们,您应该这样做。

2
对于 AngularJS 1.x 应用程序,您可以在创建 Urls 时构建一个数组,然后在组件的 $onDestroy 函数中清理它们。这对我非常有效。 - Splaktar
3
其他回答在Chrome中导致“失败:网络错误”。这个很好用。 - juniper-
6
这对我在Chrome(73.0.3683.86),Firefox(66.0.2),IE11(11.379.17763.0)和Edge(44.17763.1.0)中都奏效。 - Sam
5
对于那些希望避免 URL 垃圾回收或奇怪行为的人,只需像这样声明您的 Blob:const url = URL.createObjectURL(blob, { oneTimeOnly: true })。如果需要,您始终可以保存 Blob 并稍后生成新的 URL。 - Daniel
5
如果你想避免任何可能的视觉故障,可以考虑在 document.body.appendChild(elem); 之前添加 elem.style.display = 'none'; - JamesTheAwesomeDude
显示剩余9条评论

203

所有上述例子在Chrome和IE中都能正常工作,但在Firefox中失败。 请考虑在body后附加一个锚点,并在点击后将其删除。

var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(['Test,Text'], {type: 'text/csv'}));
a.download = 'test.csv';

// Append anchor to body.
document.body.appendChild(a);
a.click();

// Remove anchor from body
document.body.removeChild(a);

5
然而:在IE 10中存在一个未解决的bug(我仍然在11中看到),导致在a.click()这一行会抛出“访问被拒绝”的错误,因为它认为blob URL是跨域的。 - Matt
1
@Matt,据我所知,某些浏览器中的数据URI是跨域的,不仅在MSIE中如此,在Chrome中也是如此。您可以尝试使用数据URI注入JavaScript进行测试。它将无法访问站点的其他部分... - inf3rno
11
以上所有的例子在谷歌浏览器和IE中运行正常,但在Firefox中失败了。由于答案顺序可能随时间变化而改变,因此不清楚在你编写此内容时哪些答案位于你的上方。你能准确指出哪些方法在Firefox中不起作用吗? - Kevin
2
这种 Blob 方法对于非常大的文件效果更好。 - joe

135

我很高兴地使用FileSaver.js。它的兼容性相当好(IE10+和其他所有浏览器),而且非常易于使用:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");

这在Chrome上运行得很好。我如何允许用户指定磁盘上的文件位置? - gregm
6
哇,谢谢你提供如此易用的库。毫无疑问,这是最好的答案,现在又有谁会关心还有人使用HTML < 5呢? - notbad.jpeg
@gregm 我不确定你能否使用这个插件。 - Daniel Buckmaster
1
这是一个非常好的解决方案,适用于IE 10+系列浏览器。IE尚不支持下载HTML 5标签,而此页面上的其他解决方案(以及其他SO页面讨论相同问题的解决方案)对我来说都无法正常工作。FileSaver ftw! - TMc
看起来演示重新加载了页面。开发工具中的历史记录已更新。二进制八位字节看起来更好作为解决方案。 - Maxim
显示剩余4条评论

42

使用 Blob

function download(content, mimeType, filename){
  const a = document.createElement('a') // Create "a" element
  const blob = new Blob([content], {type: mimeType}) // Create a blob (file-like object)
  const url = URL.createObjectURL(blob) // Create an object URL from blob
  a.setAttribute('href', url) // Set "a" element link
  a.setAttribute('download', filename) // Set download filename
  a.click() // Start downloading
}

Blob对象被所有现代浏览器支持。
Blob的支持情况请查看Caniuse支持表:

这里是一个Fiddle

并且这里有MDN文档

Blob对象表示一个blob,它是一个不可变的、原始数据的类似文件的对象;它们可以被读取为文本或二进制数据...


22

以下方法适用于IE11+、Firefox 25+和Chrome 30+:

<a id="export" class="myButton" download="" href="#">export</a>
<script>
    function createDownloadLink(anchorSelector, str, fileName){
        if(window.navigator.msSaveOrOpenBlob) {
            var fileData = [str];
            blobObject = new Blob(fileData);
            $(anchorSelector).click(function(){
                window.navigator.msSaveOrOpenBlob(blobObject, fileName);
            });
        } else {
            var url = "data:text/plain;charset=utf-8," + encodeURIComponent(str);
            $(anchorSelector).attr("download", fileName);               
            $(anchorSelector).attr("href", url);
        }
    }

    $(function () {
        var str = "hi,file";
        createDownloadLink("#export",str,"file.txt");
    });

</script>

查看演示:http://jsfiddle.net/Kg7eA/

Firefox 和 Chrome 支持通过数据 URI 进行导航,这使我们可以通过导航到数据 URI 来创建文件,而 IE 为了安全起见不支持此功能。

另一方面,IE 有用于保存 Blob 的 API,可用于创建和下载文件。


1
我刚刚使用了jQuery来附加事件(onclick和onready)并设置属性,这也可以使用vanilla JS完成。核心部分(window.navigator.msSaveOrOpenBlob)不需要jQuery。 - dinesh ygv
1
数据 URI 方法仍然存在大小限制,是吗? - phil
msSaveOrOpenBlob在此处被标记为过时:https://developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob - eflat

22
我们可以使用URL API,特别是URL.createObjectURL()Blob API来编码和下载几乎任何东西。如果你要下载的文件很小,那么这个方法就能正常工作:

document.body.innerHTML += 
`<a id="download" download="PATTERN.json" href="${URL.createObjectURL(new Blob([JSON.stringify("HELLO WORLD", null, 2)]))}"> Click me</a>`
download.click()
download.outerHTML = ""

如果您要下载的文件很大,与其使用DOM,更好的方法是创建一个包含下载参数的链接元素,并触发点击事件。
请注意,链接元素并没有被附加到文档中,但是点击事件仍会起作用!这种方法可以创建数百兆字节大小的文件下载,因为DOM未被修改(否则DOM中巨大的URL可能导致选项卡冻结)。

const stack = {
 some: "stuffs",
 alot: "of them!"
}

BUTTONDOWNLOAD.onclick = (function(){
  let j = document.createElement("a")
  j.download = "stack_"+Date.now()+".json"
  j.href = URL.createObjectURL(new Blob([JSON.stringify(stack, null, 2)]))
  j.click()
})
<button id="BUTTONDOWNLOAD">DOWNLOAD!</button>


额外奖励!下载任何循环对象,避免错误:

TypeError:cyclic object value(Firefox)TypeError:Converting

circular structure to JSON(Chrome和Opera)TypeError:Circular

reference in value argument not supported(Edge)

使用https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

在此示例中,将document对象下载为json。

/* JSON.decycle */
if(typeof JSON.decycle!=="function"){JSON.decycle=function decycle(object,replacer){"use strict";var objects=new WeakMap();return(function derez(value,path){var old_path;var nu;if(replacer!==undefined){value=replacer(value)}
if(typeof value==="object"&&value!==null&&!(value instanceof Boolean)&&!(value instanceof Date)&&!(value instanceof Number)&&!(value instanceof RegExp)&&!(value instanceof String)){old_path=objects.get(value);if(old_path!==undefined){return{$ref:old_path}}
objects.set(value,path);if(Array.isArray(value)){nu=[];value.forEach(function(element,i){nu[i]=derez(element,path+"["+i+"]")})}else{nu={};Object.keys(value).forEach(function(name){nu[name]=derez(value[name],path+"["+JSON.stringify(name)+"]")})}
return nu}
return value}(object,"$"))}}


document.body.innerHTML += 
`<a id="download" download="PATTERN.json" href="${URL.createObjectURL(new Blob([JSON.stringify(JSON.decycle(document), null, 2)]))}"></a>`
download.click()


3
最好且易于使用的解决方案!谢谢 - enes islam

17

这个程序包 js-file-download 来自于 github.com/kennethjiang/js-file-download,它处理浏览器支持的边界情况:

点击查看源代码 以了解它如何使用本页面提到的技术。

安装

yarn add js-file-download
npm install --save js-file-download

使用方法

import fileDownload from 'js-file-download'

// fileDownload(data, filename, mime)
// mime is optional

fileDownload(data, 'filename.csv', 'text/csv')

2
谢谢 - 刚刚测试过了 - 在 Windows 上可以使用 Firefox、Chrome 和 Edge。 - Brian Burns

16

这个解决方案直接从tiddlywiki的github存储库中提取而来。我在几乎所有浏览器中使用tiddlywiki,它的表现非常出色:

function(filename,text){
    // Set up the link
    var link = document.createElement("a");
    link.setAttribute("target","_blank");
    if(Blob !== undefined) {
        var blob = new Blob([text], {type: "text/plain"});
        link.setAttribute("href", URL.createObjectURL(blob));
    } else {
        link.setAttribute("href","data:text/plain," + encodeURIComponent(text));
    }
    link.setAttribute("download",filename);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

Github仓库: 下载保存模块


1
它在Chrome上运行得非常好,但在Firefox上不行。它确实创建了一个文件并下载了它,但是文件是空的,没有内容。有什么想法为什么会这样?还没有在IE上测试过... - Narxx
4
除了这个功能没有名称之外,这是我最喜欢的。 - Yoraco Gonzales

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接