使用 HTML5/JavaScript 生成和保存文件

Posted

技术标签:

【中文标题】使用 HTML5/JavaScript 生成和保存文件【英文标题】:Using HTML5/JavaScript to generate and save a file 【发布时间】:2011-02-23 06:24:25 【问题描述】:

我最近一直在摆弄 WebGL,并让 Collada 阅读器工作。问题是它非常慢(Collada 是一种非常冗长的格式),所以我将开始将文件转换为更易于使用的格式(可能是 JSON)。我已经有了用 javascript 解析文件的代码,所以我也可以将它用作我的导出器!问题是保存。

现在,我知道我可以解析文件,将结果发送到服务器,并让浏览器从服务器请求返回文件作为下载。但实际上服务器与这个特定进程无关,那么为什么要参与其中呢?我已经在内存中有所需文件的内容。有什么方法可以让用户使用纯 JavaScript 下载? (我对此表示怀疑,但不妨问问......)

要明确一点:我不会在用户不知情的情况下访问文件系统!用户将提供一个文件(可能通过拖放),脚本将在内存中转换文件,并提示用户下载结果。就浏览器而言,所有这些都应该是“安全”的活动。

[编辑]:我没有提前提及,所以回答“Flash”的发帖人足够有效,但我正在做的部分工作是试图突出可能用纯 html5 完成......所以在我的情况下,Flash 是正确的。 (尽管对于任何做“真正的”网络应用程序的人来说,这都是一个完全有效的答案。)既然如此,除非我想涉及服务器,否则我看起来很不走运。还是谢谢!

【问题讨论】:

您可以考虑更改接受的答案,现在似乎有一种纯 HTML 方式 【参考方案1】:

对于像“txt”或“js”这样的简单文件,您可以使用包fs-browsers。 它有很好且简单的客户端下载和导出方法,不涉及任何服务器。

import  exportFile  from 'fs-browsers';
const onExportClick = (textToExport) => 
  // Export to txt file
  exportFile(textToExport);

如果您想更改文件的名称,甚至是类型,您可以通过以下方式轻松完成:

import  exportFile  from 'fs-browsers';
const onExportClick = (textToExport) => 
  // Export to js file called 'file.js'
  exportFile(textToExport,  fileName: 'file.js' );

如您所说,对于更复杂的文件,您需要使用服务器。 如果您需要,该软件包还可以使用 excel 文件 ('xls') 执行此操作。

import  exportFile, EXCEL_FILE  from 'fs-browsers';
const data = [ "id": 5, "name": "John", "grade": 90, "age": 15 ,  "id": 7, "name": "Nick", "grade": 70, "age": 17 ];
const headings = ["Student ID", "Student Name", "Test Grade", "Student Age"];
exportFile(data,  type: EXCEL_FILE, headings: headings, fileName: 'grades.xls' );

也许将来还会有其他类型的文件。

【讨论】:

【参考方案2】:

在测试“ahref”方法时,我发现 Firefox 和 Chrome 的 web 开发者工具很混乱。发出 a.click() 后,我需要重新启动调试。 FileSaver 也是如此(它使用相同的 ahref 方法来实际进行保存)。为了解决这个问题,我创建了新的临时窗口,将元素 a 添加到其中并在其中单击它。

    function download_json(dt) 
        var csv = ' var data = ';
        csv += JSON.stringify(dt, null, 3);

        var uricontent = 'data:application/octet-stream,' + encodeURI(csv);

        var newwin = window.open( "", "_blank" );
        var elem = newwin.document.createElement('a');
        elem.download = "database.js";
        elem.href = uricontent;
        elem.click();
        setTimeout(function() newwin.close(); , 3000);
        
    

【讨论】:

【参考方案3】:

试试

let a = document.createElement('a');
a.href = "data:application/octet-stream,"+encodeURIComponent('"My DATA"');
a.download = 'myFile.json';
a.click(); // we not add 'a' to DOM so no need to remove

如果你想下载二进制数据看here

更新

2020.06.14 我将 Chrome 升级到 83.0 及更高版本所以 sn-p 停止工作(由于 sandbox security 限制) - 但 JSFiddle 版本工作 - here

【讨论】:

【参考方案4】:

这个线程对于弄清楚如何生成二进制文件并提示下载命名文件非常有用,所有这些都在没有服务器的客户端代码中。

对我来说,第一步是从我保存的数据中生成二进制 blob。对于单个二进制类型有很多示例,在我的情况下,我有一个具有多种类型的二进制格式,您可以将其作为数组传递以创建 blob。

saveAnimation: function() 

    var device = this.Device;
    var maxRow = ChromaAnimation.getMaxRow(device);
    var maxColumn = ChromaAnimation.getMaxColumn(device);
    var frames = this.Frames;
    var frameCount = frames.length;

    var writeArrays = [];


    var writeArray = new Uint32Array(1);
    var version = 1;
    writeArray[0] = version;
    writeArrays.push(writeArray.buffer);
    //console.log('version:', version);


    var writeArray = new Uint8Array(1);
    var deviceType = this.DeviceType;
    writeArray[0] = deviceType;
    writeArrays.push(writeArray.buffer);
    //console.log('deviceType:', deviceType);


    var writeArray = new Uint8Array(1);
    writeArray[0] = device;
    writeArrays.push(writeArray.buffer);
    //console.log('device:', device);


    var writeArray = new Uint32Array(1);
    writeArray[0] = frameCount;
    writeArrays.push(writeArray.buffer);
    //console.log('frameCount:', frameCount);

    for (var index = 0; index < frameCount; ++index) 

      var frame = frames[index];

      var writeArray = new Float32Array(1);
      var duration = frame.Duration;
      if (duration < 0.033) 
        duration = 0.033;
      
      writeArray[0] = duration;
      writeArrays.push(writeArray.buffer);

      //console.log('Frame', index, 'duration', duration);

      var writeArray = new Uint32Array(maxRow * maxColumn);
      for (var i = 0; i < maxRow; ++i) 
        for (var j = 0; j < maxColumn; ++j) 
          var color = frame.Colors[i][j];
          writeArray[i * maxColumn + j] = color;
        
      
      writeArrays.push(writeArray.buffer);
    

    var blob = new Blob(writeArrays, type: 'application/octet-stream');

    return blob;

下一步是让浏览器提示用户下载这个具有预定义名称的 blob。

我只需要一个我在 HTML5 中添加的命名链接,我可以重复使用它来重命名初始文件名。我把它隐藏了,因为链接不需要显示。

<a id="lnkDownload" style="display: none" download="client.chroma" href="" target="_blank"></a>

最后一步是提示用户下载文件。

var data = animation.saveAnimation();
var uriContent = URL.createObjectURL(data);
var lnkDownload = document.getElementById('lnkDownload');
lnkDownload.download = 'theDefaultFileName.extension';
lnkDownload.href = uriContent;
lnkDownload.click();

【讨论】:

【参考方案5】:

简单的解决方案!

&lt;a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD"&gt;Click here&lt;/a&gt;

适用于所有现代浏览器。

【讨论】:

您好,您知道如何使用 window.open 或其他 javascript 函数指定“下载”属性行为吗? @yucer window.open() 没有下载属性(或任何相关属性)。这无关紧要。您可以使用此方法并在其上强制使用.click(),但如果您在让元素附加到正文之前调用.click(),请注意时间,因为Firefox 不喜欢它。 但遗憾的是所有空格都被删除了。就我而言,这真的很糟糕,因为我想下载 SVG 文件的源代码。 如果你使用 encodeURIComponent(content) 会保留空格 无法在 Firefox 中选择名称,但 chrome 可以使用【参考方案6】:

保存大文件

长数据 URI 可能会给浏览器带来性能问题。保存客户端生成文件的另一种选择是将其内容放入 Blob(或文件)对象并使用 URL.createObjectURL(blob) 创建下载链接。这将返回一个可用于检索 blob 内容的 URL。 Blob 存储在浏览器中,直到在 URL 上调用 URL.revokeObjectURL() 或关闭创建它的文档。大多数网络浏览器都有support for object URLs,Opera Mini 是唯一不支持的。

强制下载

如果数据是文本或图像,浏览器可以打开文件,而不是将其保存到磁盘。要在单击链接时下载文件,您可以使用download 属性。然而,并不是所有的网络浏览器都有support for the download attribute。另一种选择是使用application/octet-stream 作为文件的mime 类型,但这会导致文件显示为二进制blob,如果您不指定或不能指定文件名,这对用户特别不友好。另见“Force to open "Save As..." popup open at text link click for pdf in HTML”。

指定文件名

如果使用 File 构造函数创建 blob,您还可以设置文件名,但只有少数 Web 浏览器(包括 Chrome 和 Firefox)有 support for the File constructor。文件名也可以指定为download 属性的参数,但这取决于大量security considerations。 Internet Explorer 10 和 11 提供了自己的方法 msSaveBlob 来指定文件名。

示例代码

var file;
var data = [];
data.push("This is a test\n");
data.push("Of creating a file\n");
data.push("In a browser\n");
var properties = type: 'text/plain'; // Specify the file's mime-type.
try 
  // Specify the filename using the File constructor, but ...
  file = new File(data, "file.txt", properties);
 catch (e) 
  // ... fall back to the Blob constructor if that isn't supported.
  file = new Blob(data, properties);

var url = URL.createObjectURL(file);
document.getElementById('link').href = url;
&lt;a id="link" target="_blank" download="file.txt"&gt;Download&lt;/a&gt;

【讨论】:

我可以显示一个对话框(弹出)来指定一个文件夹(目录)来保存文件吗? @Calvin:我更新了答案以解释如何强制下载并提供文件名。对于IE,相信你可以使用msSaveBlob打开“另存为”对话框。对于其他浏览器,您唯一的选择是从下载链接的上下文菜单中手动选择“另存为”。 @Jek-fdrv:只有 Blob-url 可以在 Safari 中使用。 Safari 不支持 download 属性和 File 构造函数,因此您无法强制下载,这意味着该 blob 可能会在浏览器本身中打开,并且您无法指定文件名。对于给定的示例,您应该仍然能够使用 Safari 使用链接的上下文菜单下载文件。 @bcmpinc ***.com/questions/36444507/… 这是一个非常有用且内容丰富的答案。还有一件事可能对像我这样的人有所帮助:设置 document.getElementById('link').href = url; 后,您的代码可以继续使用元素的 .click() 方法触发链接。【参考方案7】:

这是一个将文件导出为 ZIP 的教程:

在开始之前,有一个用来保存文件的库,库的名字是fileSaver.js,你可以在这里找到这个库。让我们开始吧,现在,包括所需的库:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.4/jszip.min.js"  type="text/javascript"></script>
<script type="text/javascript" src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.js" ></script>

现在复制这段代码,这段代码将下载一个 zip 文件,其中的文件 hello.txt 包含内容 Hello World。如果一切正常,这将下载一个文件。

<script type="text/javascript">
    var zip = new JSZip();
    zip.file("Hello.txt", "Hello World\n");
    zip.generateAsync(type:"blob")
    .then(function(content) 
        // see FileSaver.js
        saveAs(content, "file.zip");
    );
</script>

这将下载一个名为 file.zip 的文件。你可以在这里阅读更多:http://www.wapgee.com/story/248/guide-to-create-zip-files-using-javascript-by-using-jszip-library

【讨论】:

【参考方案8】:

如前所述,File API 以及 FileWriter 和 FileSystem API 可用于从浏览器选项卡/窗口的上下文将文件存储在客户端计算机上。

但是,您应该注意与后两个 API 有关的几件事:

API 的实现目前仅存在于基于 Chromium 的浏览器(Chrome 和 Opera)中 这两个 API 均已于 2014 年 4 月 24 日脱离 W3C 标准轨道,并且现在是专有的 将来有可能从实现浏览器中删除(现在是专有的)API 沙盒(磁盘上的一个位置,文件在该位置之外不会产生任何影响)用于存储使用 API 创建的文件 使用虚拟文件系统(在磁盘上的目录结构不一定以与从浏览器中访问时相同的形式存在)表示使用 API 创建的文件李>

以下是如何直接或间接地串联使用 API 来执行此操作的简单示例:

BakedGoods*

bakedGoods.get(
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: fileSystem:storageType: Window.PERSISTENT,
        complete: function(resultDataObj, byStorageTypeErrorObj)
);

使用原始 File、FileWriter 和 FileSystem API

function onQuotaRequestSuccess(grantedQuota)


    function saveFile(directoryEntry)
    

        function createFileWriter(fileEntry)
        

            function write(fileWriter)
            
                var dataBlob = new Blob(["Hello world!"], type: "text/plain");
                fileWriter.write(dataBlob);              
            

            fileEntry.createWriter(write);
        

        directoryEntry.getFile(
            "testFile", 
            create: true, exclusive: true,
            createFileWriter
        );
    

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);


var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);

尽管 FileSystem 和 FileWriter API 不再符合标准,但在我看来,它们的使用在某些情况下是合理的,因为:

未实施的浏览器供应商重新燃起兴趣可能会让他们重新关注它 实施(基于 ​​Chromium 的)浏览器的市场渗透率很高 Google(Chromium 的主要贡献者)尚未给出 API 的生命周期终止日期

但是,“某些情况”是否包含您自己的情况,由您决定。

*BakedGoods 由这里的这个人维护 :)

【讨论】:

【参考方案9】:

好的,创建一个 data:URI 确实对我有用,感谢 Matthew 和 Dennkster 指出了该选项!这基本上是我的做法:

1) 将所有内容放入一个名为“content”的字符串中(例如,通过最初在那里创建它或通过读取已构建页面的标签的 innerHTML)。

2) 构建数据 URI:

uriContent = "data:application/octet-stream," + encodeURIComponent(content);

会有长度限制,具体取决于浏览器类型等,但例如Firefox 3.6.12 至少可以工作到 256k。在 Base64 中编码而不是使用 encodeURIComponent 可能会使事情变得更高效,但对我来说没问题。

3) 打开一个新窗口并将其“重定向”到此 URI 提示我的 JavaScript 生成页面的下载位置:

newWindow = window.open(uriContent, 'neuesDokument');

就是这样。

【讨论】:

如果您想避免使用可能被阻止的弹出窗口,您可以将location.href 设置为内容。 location.href = uriContent. 您好,我试过了,但它会将文件下载为 .part 文件。如何设置文件类型? @SedatBaşar 数据 URI 不支持设置文件名,您必须依靠浏览器使用 mime 类型设置适当的扩展名。但是如果 mime 类型可以被浏览器渲染,它就不会下载它,但它会显示它。还有其他一些方法可以做到这一点,但在 IE 中都不起作用 IE 并不真正支持数据 URI,而且 Firefox 似乎使用随机名称保存文件。 我认为我们让这变得比需要的更困难。在此页面上打开您的 JS 控制台并将其输入location.href = "data:application/octet-stream," + encodeURIComponent(jQuery('#answer-4551467 .post-text').first().text());,它会将@Nøk 答案的内容保存到文件中。我没有 IE 来测试它,但它适用于 webkit。【参考方案10】:

HTML5 浏览器的简单解决方案...

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

    if (document.createEvent) 
        var event = document.createEvent('MouseEvents');
        event.initEvent('click', true, true);
        pom.dispatchEvent(event);
    
    else 
        pom.click();
    

用法

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

【讨论】:

如果您知道要下载的源网址,这是最好的解决方案! 设置文件名的能力使它成为赢家。 这种方法在 Chrome 中一直有效,直到我几天前收到的最新更新 (35.0.1916.114 m)。现在它部分起作用了,文件名和扩展名不再起作用(无论传递什么,它总是将文件命名为 download.txt。) 我有 Chrom 42.0.2311.90 ,这对我来说是预期的文件名。 如果可以包含的数据量有限制?【参考方案11】:

我发现了两种对我有用的简单方法。首先,使用已点击的a 元素并注入下载数据。其次,使用下载数据生成a 元素,执行a.click() 并再次删除它。但第二种方法也只有在用户点击操作调用时才有效。 (某些)浏览器阻止 click() 来自其他上下文,例如加载时或超时后触发 (setTimeout)。

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript">
      function linkDownload(a, filename, content) 
        contentType =  'data:application/octet-stream,';
        uriContent = contentType + encodeURIComponent(content);
        a.setAttribute('href', uriContent);
        a.setAttribute('download', filename);
      
      function download(filename, content) 
        var a = document.createElement('a');
        linkDownload(a, filename, content);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      
    </script>
   </head>
  <body>
    <a href="#" onclick="linkDownload(this, 'test.txt', 'Hello World!');">download</a>
    <button onclick="download('test.txt', 'Hello World!');">download</button>
  </body>
</html>

【讨论】:

您也可以将a 插入到文档中(可能带有"display:none"),单击它,然后将其删除。 这项功能是否适用于不支持下载属性的浏览器,例如现代 ie 和 safari..caniuse.com/#feat=download 刚刚在 wine 下测试了 Safari 5.0。第一个版本有效,第二个无效。我也测试了 IE 8 (wine),但它不起作用。【参考方案12】:

HTML5 定义了一个window.saveAs(blob, filename) 方法。目前任何浏览器都不支持它。但是有一个名为FileSaver.js 的兼容性库,可以将此功能添加到大多数现代浏览器(包括 Internet Explorer 10+)。 Internet Explorer 10 支持 navigator.msSaveBlob(blob, filename) 方法 (MSDN),它在 FileSaver.js 中用于 Internet Explorer 支持。

我写了一封blog posting,详细说明了这个问题。

【讨论】:

阻止弹出窗口怎么样?这个库的行为类似于@Nøk 的解决方案。 Firefox 中的纯文本以新方式打开。只有 Chrome 会尝试保存它,但它会更改扩展名(我需要一个没有扩展名的点文件)。 @ciembor (object url+)download 属性变体(我在 chrome 中使用)允许您设置文件名。它适用于我的 chrome。 @ciembor 啊哈,如果点击直接导致弹出窗口不会被阻止。 FileSaver.js 现在支持 IE W3C 说:本文档的工作已经停止,不应被引用或用作实施的基础。【参考方案13】:

我使用过 FileSaver (https://github.com/eligrey/FileSaver.js),它运行良好。 例如,我做了这个功能来导出页面上显示的日志。 您必须为 Blob 的实例化传递一个数组,所以我可能没有以正确的方式编写此代码,但它对我有用。 以防万一,替换时要小心:这是使 this 全局化的语法,否则它只会替换他遇到的第一个。

exportLogs : function()
    var array = new Array();

    var str = $('#logs').html();
    array[0] = str.replace(/<br>/g, '\n\t');

    var blob = new Blob(array, type: "text/plain;charset=utf-8");
    saveAs(blob, "example.log");

【讨论】:

FileSaver 很棒,这里有一个 IE Shim 用于 pre-IE10 功能 preIE10SaveAs: (filename, filecontent, mimetype) var w = window.open(); var doc = w.document; doc.open(mimetype,'replace'); doc.charset = "utf-8"; doc.write(文件内容); doc.close(); doc.execCommand("另存为", null, 文件名); 关于@aqm shim 的警告:它执行脚本标签。 另外,可能需要将w.close(); 放在execCommand 之后【参考方案14】:
function download(content, filename, contentType)

    if(!contentType) contentType = 'application/octet-stream';
        var a = document.createElement('a');
        var blob = new Blob([content], 'type':contentType);
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.click();

【讨论】:

contentType 的作用是什么?它是干什么用的? 即使在最新的 Chrome 中也能正常工作,这与@Matěj Pokorný 的回答不同。谢谢。 这对我在 FF36 或 IE11 上不起作用。如果我按照 Matěj Pokorný 的建议将 a.click 替换为使用 document.createEvent() 的代码,它适用于 FF,但不适用于 IE。我没试过 Chrome。【参考方案15】:

看看 Doug Neiner 的 Downloadify,这是一个基于 Flash 的 JavaScript 界面来执行此操作。

Downloadify 是一个小型 JavaScript + Flash 库,可以在浏览器中动态生成和保存文件,无需服务器交互。

【讨论】:

对于大多数人来说,这可能是他们需要的答案。因此,即使它不符合我的具体要求(如上所述),我也会将其标记为已接受的答案。 @Toji 啊,我明白了。也许在HTML 5 横幅下重新询问和重新措辞并相应地标记?这可能会吸引那些了解该特定领域的用户(我想现在仍然是一个相对较小的人群)。我很确定它可以在 HTML 5 中完成,但我不知道如何。 downloadify.infoDownloadify 域是否已购买/转让,如果有,是否有新位置?目前的网站似乎与给出的答案完全无关。 这没有回答 Using HTML5... - 标题问题。 @Ixx 公平地说,这是在发布答案后添加的。不过,你是对的。下面的答案应该被接受【参考方案16】:

这是 Mathew 建议的数据 URI 方法的链接,它在 safari 上工作,但效果不佳,因为我无法设置文件类型,它被保存为“未知”,然后我必须稍后再去那里进行更改它是为了查看文件...

http://www.nihilogic.dk/labs/canvas2image/

【讨论】:

【参考方案17】:

您可以使用本地存储。这是 cookie 的 Html5 等价物。它似乎可以在 Chrome 和 Firefox 上运行,但在 Firefox 上,我需要将它上传到服务器。也就是说,直接在我家的电脑上测试是行不通的。

我正在编写 HTML5 示例。转至http://faculty.purchase.edu/jeanine.meyer/html5/html5explain.html 并滚动到迷宫之一。重建迷宫的信息使用 localStorage 存储。

我来这篇文章是为了寻找用于加载和处理 xml 文件的 HTML5 JavaScript。和老的html和JavaScript一样吗???

【讨论】:

【参考方案18】:

您可以生成data URI。但是,存在特定于浏览器的限制。

【讨论】:

这很有趣。当我有机会时,我会更多地研究它。谢谢! @quantumpotato,实际上生成 URL 有点难以解释。所有细节都在RFC 2397 中。您可以使用this 等工具进行测试。然后,对于您的真实应用程序,您可以为您的语言搜索数据 URI 或 base 64 库。如果您没有找到它,请随时提出后续问题。 Wikipedia article 中给出了一些特定于浏览器的限制。例如,IE 限制了长度和类型(例如不是 text/html)。 生成数据 URL 并不是那么棘手:"data:text/plain;charset=UTF-8,"+encodeURIComponent(text) 但是,是的,IE 将数据 URL 的大小限制为无法使用的数量,并且它不支持 window.open(...) 或 iframe(我想想)。 @panzi,也没有那么容易。一方面,这不是 Collada 或 JSON 的正确 MIME 类型。 太无信息的答案。如果您提到它们,请添加浏览器的规范。

以上是关于使用 HTML5/JavaScript 生成和保存文件的主要内容,如果未能解决你的问题,请参考以下文章

HTML5 Javascript:将文本保存到画布上?

如何在 iPad 上使用 HTML5/Javascript 合成音频

如何在iPad上使用HTML5 / Javascript合成音频

要生成像 iOS 8 Health 应用那样的图表,都有哪些比较好用的库

如何在HTML5 / JavaScript中加载/卸载图像

Can (Html5,javascript,css3) 应用程序将在 ios 和 android 上完美运行