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

Posted

技术标签:

【中文标题】如何在内存中创建一个文件供用户下载,而不是通过服务器?【英文标题】:How to create a file in memory for user to download, but not through server? 【发布时间】:2011-04-09 13:41:34 【问题描述】:

有什么方法可以在客户端创建一个文本文件并提示用户下载它,而无需与服务器进行任何交互? 我知道我不能直接写入他们的机器(安全和所有),但我可以创建并提示他们保存吗?

【问题讨论】:

自 2014 年 4 月起,FileSytem API 可能未在 W3C 中标准化。我想,任何使用 blob 解决方案的人都应该谨慎行事。 html5 rocks heads upW3C Mailing List on FileSytem API 另见:javascript: Create and save file 【参考方案1】:

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!');

【讨论】:

是的。这正是@MatthewFlaschen 所拥有的posted here about 3 years ago。 是的,但是使用download 属性可以指定文件名;-) 正如@earcam 已经指出的in the comments above。 如果您没有在文件名中提供扩展名,Chrome 只会附加 txt 扩展名。如果您执行download("data.json", data),它将按预期工作。 这在 Chrome (73.0.3683.86) 和 Firefox (66.0.2) 中对我有用。它在 IE11 (11.379.17763.0) 和 Edge (44.17763.1.0) 中确实工作。【参考方案2】:

您可以使用数据 URI。浏览器支持各不相同;见Wikipedia。示例:

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

八位字节流是强制下载提示。否则,它可能会在浏览器中打开。

对于 CSV,您可以使用:

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

试试jsFiddle demo。

【讨论】:

这不是跨浏览器解决方案,但绝对值得一看。例如 IE 限制对数据 uri 的支持。 IE 8 将大小限制为 32KB,而 IE 7 及更低版本根本不支持。 在 Chrome 版本 19.0.1084.46 中,此方法会生成以下警告:“资源解释为文档但使用 MIME 类型 text/csv 传输:”data:text/csv,field1%2Cfield2%0Afoo%2Cbar %0Agoo%2Cgai%0A"。"未触发下载 它现在可以在 Chrome 中工作(针对 v20 和 v21 进行测试)但不能在 IE9 中工作(可能只是 jsFiddle,但不知何故我对此表示怀疑)。 正确的字符集几乎肯定是 UTF-16,除非您有代码将其转换为 UTF-8。 JavaScript 在内部使用 UTF-16。如果您有文本或 CSV 文件,请以 '\ufeff' 开头的字符串,UTF-16BE 的字节顺序标记,文本编辑器将能够正确读取非 ASCII 字符。 只需添加 download="txt.csv" 属性以获得正确的文件名和扩展名,并告诉您的操作系统如何处理它。【参考方案3】:

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() 来释放它们中的每一个。当文档被卸载时,浏览器会自动释放它们;但是,为了获得最佳性能和内存使用率,如果您可以在安全时间显式卸载它们,则应该这样做。

【讨论】:

对于 AngularJS 1.x 应用程序,您可以在创建 URL 时构建一个数组,然后在组件的 $onDestroy 函数中清理它们。这对我很有用。 其他答案导致 Chrome 中的Failed: network error。这个效果很好。 这适用于我的 Chrome (73.0.3683.86)、Firefox (66.0.2)、IE11 (11.379.17763.0) 和 Edge (44.17763.1.0)。 对于那些希望避免 URL 上的垃圾收集或奇怪行为的人,只需像这样声明您的 blob:const url = URL.createObjectURL(blob, oneTimeOnly: true )。如果需要,您可以随时保存 blob 并在以后生成新的 Url。 如果您想避免任何潜在的视觉故障,请考虑在document.body.appendChild(elem); 之前添加elem.style.display = 'none';【参考方案4】:

上述所有示例在 chrome 和 IE 中都可以正常工作,但在 Firefox 中失败。 请考虑在主体上附加一个锚点并在单击后将其删除。

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);

【讨论】:

但是:an open bug in IE 10 (and I've still seen it in 11) 在 a.click() 行上抛出“访问被拒绝”,因为它认为 blob URL 是跨域的。 @Matt data uri 在某些浏览器中是跨源的。据我所知,不仅在 msie 中,而且在 chrome 中也是如此。您可以通过尝试使用数据 uri 注入 javascript 来测试它。它将无法访问网站的其他部分... "上述所有示例在 chrome 和 IE 中都可以正常工作,但在 Firefox 中失败。"。由于答案的顺序会随着时间的推移而改变,所以当你写这篇文章时,不清楚哪些答案高于你的答案。您能否准确指出哪些方法在 Firefox 中不起作用? ? 这种 blob 方法对于非常大的文件效果更好。【参考方案5】:

我很高兴使用FileSaver.js。它的兼容性很好(IE10+ 和其他),而且使用起来非常简单:

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

【讨论】:

这在 Chrome 上效果很好。如何允许用户指定文件在磁盘上的位置? 哇,感谢易于使用的库。这无疑是最好的答案,谁在乎人们现在以任何方式使用 HTML @gregm 我不确定你是否可以使用这个插件。 @gregm:你的意思是下载位置?这与 FileSaver.js 无关,您需要设置浏览器配置,以便在每次下载之前要求一个文件夹,或者在 &lt;a&gt; 上使用相当新的 download attribute。 这是 IE 10+ 系列浏览器的绝佳解决方案。 IE 还不支持下载 HTML 5 标记,并且此页面上的其他解决方案(以及讨论相同问题的其他 SO 页面)根本不适合我。 FileSaver ftw!【参考方案6】:

以下方法适用于 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,可用于创建和下载文件。

【讨论】:

我只是使用 jquery 来附加事件(onclick 和 onready)并设置属性,您也可以使用 vanilla JS 来执行此操作。核心部分(window.navigator.msSaveOrOpenBlob)不需要jquery。 data uri 方式还是有大小限制的吧? msSaveOrOpenBlob 在此处显示为已过时:developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob【参考方案7】:

来自github.com/kennethjiang/js-file-download 的包js-file-download 处理浏览器支持的边缘情况:

View source 看看它如何使用本页提到的技术。

安装

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')

【讨论】:

谢谢 - 刚刚测试过 - 适用于 Windows 上的 Firefox、Chrome 和 Edge【参考方案8】:

此解决方案直接从 tiddlywiki 的 (tiddlywiki.com) 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 存储库: Download saver module

【讨论】:

它在 Chrome 上运行得非常好,但在 Firefox 上却不行。它确实会创建一个文件并下载它,但该文件是空的。无内容。任何想法为什么?没有在 IE 上测试过... 除了函数没有名字,这是我最喜欢的【参考方案9】:

我们可以使用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,而是使用下载参数创建一个链接元素,然后触发点击。

请注意,链接元素并未附加到文档中,但点击仍然有效!通过这种方式可以创建数百个 Mo 的下载。

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()
)
&lt;button id="BUTTONDOWNLOAD"&gt;DOWNLOAD!&lt;/button&gt;

奖励!下载任何cyclic objects,避免错误:

TypeError: 循环对象值 (Firefox) TypeError: Converting

JSON 的循环结构(Chrome 和 Opera)TypeError: Circular

不支持值参数中的引用(边缘)

使用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+"]"))elsenu=;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()

【讨论】:

【参考方案10】:

如果您只想将字符串转换为可供下载,您可以尝试使用 jQuery。

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));

【讨论】:

可能需要使用 encodeURI 的 Scape 数据,正如我在这里建议的那样,然后才能发表评论:***.com/a/32441536/4928558【参考方案11】:

适用于 IE10 的解决方案: (我需要一个csv文件,但是将类型和文件名更改为txt就足够了)

var csvContent=data; //here we load our csv data 
var blob = new Blob([csvContent],
    type: "text/csv;charset=utf-8;"
);

navigator.msSaveBlob(blob, "filename.csv")

【讨论】:

Ludovic's answer 包括这个大,加上对其他浏览器的支持。【参考方案12】:

如前所述,filesaver 是一个很棒的包,可以在客户端处理文件。但是,它不适用于大文件。 StreamSaver.js 是可以处理大文件的替代解决方案(在 FileServer.js 中指出):

const fileStream = streamSaver.createWriteStream('filename.txt', size);
const writer = fileStream.getWriter();
for(var i = 0; i < 100; i++)
    var uint8array = new TextEncoder("utf-8").encode("Plain Text");
    writer.write(uint8array);

writer.close()

【讨论】:

文本编码器现在是高度实验性的,我建议避免(或填充)它。【参考方案13】:
var element = document.createElement('a');
element.setAttribute('href', 'data:text/text;charset=utf-8,' +      encodeURI(data));
element.setAttribute('download', "fileName.txt");
element.click();

【讨论】:

这种方法和创建 Blob 有什么区别?【参考方案14】:

基于@Rick 的回答,这真的很有帮助。

如果你想以这种方式分享,你必须对字符串 data 进行转义:

$('a.download').attr('href', 'data:application/csv;charset=utf-8,'+ encodeURI(data));

` 抱歉,由于我目前在 *** 中的声誉很低,我无法评论 @Rick 的回答。

edit suggestion 被分享并被拒绝。

【讨论】:

我无法接受这个建议。奇怪...我更新了代码。【参考方案15】:
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);




// Start file download.
download("hello.txt","This is the content of my file :)");

原文:https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server

【讨论】:

【参考方案16】:

使用Blob

function download(content, mimeType, filename)
  const a = document.createElement('a')
  const blob = new Blob([content], type: mimeType)
  const url = URL.createObjectURL(blob)
  a.setAttribute('href', url)
  a.setAttribute('download', filename)
  a.click()

所有现代浏览器都支持 Blob。 Blob 的 Caniuse 支持表:

Here is a Fiddle

【讨论】:

【参考方案17】:

下面这个函数起作用了。

 private createDownloadableCsvFile(fileName, content) 
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,$content`;
   return link;
 

【讨论】:

你能在新标签页中打开文件,保持分配的文件名,但不下载,只是在标签页中打开吗?【参考方案18】:

以下方法适用于IE10+、Edge、Opera、FF和Chrome:

const saveDownloadedData = (fileName, data) => 
    if(~navigator.userAgent.indexOf('MSIE') || ~navigator.appVersion.indexOf('Trident/'))  /* IE9-11 */
        const blob = new Blob([data],  type: 'text/csv;charset=utf-8;' );
        navigator.msSaveBlob(blob, fileName);
     else 
        const link = document.createElement('a')
        link.setAttribute('target', '_blank');
        if(Blob !== undefined) 
            const blob = new Blob([data],  type: 'text/plain' );
            link.setAttribute('href', URL.createObjectURL(blob));
         else 
            link.setAttribute('href', 'data:text/plain,' + encodeURIComponent(data));
        

        ~window.navigator.userAgent.indexOf('Edge')
            && (fileName = fileName.replace(/[&\/\\#,+$~%.'':*?<>]/g, '_')); /* Edge */

        link.setAttribute('download', fileName);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    

所以,只需调用函数:

saveDownloadedData('test.txt', 'Lorem ipsum');

【讨论】:

【参考方案19】:

对我来说,这非常有效,下载了相同的文件名和扩展名

&lt;a href="data:application/octet-stream;charset=utf-16le;base64," + file64 download=title &gt;title&lt;/a&gt;

'title'是带有扩展名的文件名,即sample.pdfwaterfall.jpg等。

'file64' 是类似这样的 base64 内容,即Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO

【讨论】:

【参考方案20】:

我会使用&lt;a&gt;&lt;/a&gt; 标签,然后设置href='path'。然后,在&lt;a&gt; 元素之间放置一个图像,以便我可以看到它。如果你愿意,你可以创建一个函数来改变href,这样它就不仅仅是同一个链接,而是动态的。

如果您想使用 javascript 访问它,请给 &lt;a&gt; 标记加上 id

从 HTML 版本开始:

<a href="mp3/tupac_shakur-how-do-you-want-it.mp3" download id="mp3Anchor">
     <img src="some image that you want"    />
</a>

现在使用 JavaScript:

*Create a small json file*;

const array = [
     "mp3/tupac_shakur-how-do-you-want-it.mp3",
     "mp3/spice_one-born-to-die.mp3",
     "mp3/captain_planet_theme_song.mp3",
     "mp3/tenchu-intro.mp3",
     "mp3/resident_evil_nemesis-intro-theme.mp3"
];

//load this function on window
window.addEventListener("load", downloadList);

//now create a function that will change the content of the href with every click
function downloadList() 
     var changeHref=document.getElementById("mp3Anchor");

     var j = -1;

     changeHref.addEventListener("click", ()=> 

           if(j < array.length-1) 
               j +=1;
               changeHref.href=""+array[j];
          
           else 
               alert("No more content to download");
          

【讨论】:

【参考方案21】:

在示例中下载带扩展名或不带扩展名的文件,我使用的是 JSON。您可以添加您的数据和扩展。您可以根据自己的意愿在此处使用“MAC-Addresses.json”。如果要添加扩展名,请在此处添加,否则,只需写入不带扩展名的文件名即可。

let myJson = JSON.stringify(yourdata);
    let element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(myJson));
    element.setAttribute('download', 'MAC-Addresses.json');
    element.style.display = 'none';
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);

【讨论】:

【参考方案22】:

如果文件包含文本数据,我使用的一种技术是将文本放入 textarea 元素并让用户选择它(单击 textarea 然后按 ctrl-A)然后复制,然后粘贴到文本编辑器。

【讨论】:

我曾考虑过,但从用户友好的角度来看,这是灾难性的。此外,该文件必须以 CSV 扩展名保存。试着告诉你的用户。

以上是关于如何在内存中创建一个文件供用户下载,而不是通过服务器?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Javascript中创建动态文件+链接以供下载? [复制]

在使用 Flask 的 python 中,如何写出一个对象以供下载?

Spring MVC - 在内存中创建一个ZIP文件,让用户下载

如何在Dynamic CRM 2013中创建WebService接口供其它系统调用

如何在内存中创建一个新的 java.io.File? [复制]

在内存中创建 eml 文件而不将其保存在磁盘上