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

我最近一直在摆弄WebGL,并得到了一个Collada阅读器工作。问题是它非常慢(Collada是一种非常冗长的格式),所以我将开始将文件转换为更容易使用的格式(可能是JSON)。我已经有代码来解析JavaScript文件,所以我不妨使用它作为我的出口商太!问题在于储蓄。

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

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

[编辑]:我没有在前面提到它,所以那些回答“Flash”的海报是足够有效的,但我所做的一部分是试图强调纯HTML5可以做什么…所以闪电侠正好适合我。(尽管这对于任何制作“真正的”web应用程序的人来说都是一个非常有效的答案)在这种情况下,除非我想要涉及服务器,否则我看起来很不走运。谢谢!

478784 次浏览

看看Doug Neiner的Downloadify,这是一个基于Flash的JavaScript接口。

downloadadify是一个很小的JavaScript + Flash库,可以在浏览器中生成和保存文件,而不需要服务器交互。

你可以生成一个数据URI。但是,存在特定于浏览器的限制。

您可以使用localStorage。这相当于Html5中的cookie。它似乎可以在Chrome和Firefox上运行,但在Firefox上,我需要将它上传到服务器上。也就是说,直接在我的家用电脑上测试不起作用。

我正在做HTML5的例子。去http://faculty.purchase.edu/jeanine.meyer/html5/html5explain.html 转到迷宫一。重建迷宫的信息使用localStorage存储

我读这篇文章是为了寻找用于加载和处理xml文件的HTML5 JavaScript。它是否与旧的html和JavaScript相同????

这里有一个链接到Mathew建议的数据URI方法,它在safari上工作,但不是很好,因为我不能设置文件类型,它被保存为“未知”,然后我不得不再次去那里,改变它,以便查看文件…

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

好的,创建一个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');

就是这样。

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

我写了一个博客关于这个问题的更多细节。

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

我已经使用了FileSaver (https://github.com/eligrey/FileSaver.js),它工作得很好
.
.
.
.
. 你必须传递一个数组用于Blob的实例化,我可能写得不对,但这对我来说是可行的
为了以防万一,请注意replace:这是使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");
}

我发现了两种简单的方法。首先,使用已经单击的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>

保存大文件

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

强制下载

如果数据是文本或图像,浏览器可以打开文件,而不是将其保存到磁盘。要使文件在单击链接时被下载,可以使用download属性。然而,并不是所有的浏览器都有支持下载属性。另一种选择是使用application/octet-stream作为文件的mime类型,但这将导致文件以二进制blob形式显示,如果你没有或不能指定文件名,这对用户尤其不友好。参见'强制打开“另存为”;弹出打开在文本链接点击pdf在HTML'。

指定文件名

如果blob是用File构造函数创建的,你也可以设置一个文件名,但只有少数web浏览器(包括Chrome &Firefox)有File构造函数的支持。文件名也可以指定为download属性的参数,但这需要大量的安全注意事项。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;
<a id="link" target="_blank" download="file.txt">Download</a>

如前所述,文件 API以及FileWriter文件系统 API可用于从浏览器选项卡/窗口的上下文中存储客户端机器上的文件。

然而,关于后两个api,有几件事你应该知道:

  • api的实现目前只存在于基于Chrome的浏览器中(Chrome &歌剧)
  • 这两个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){}
});

使用原始的文件、FileWriter和文件系统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的)浏览器的市场渗透率很高
  • 谷歌(Chromium的主要贡献者)没有给出api的生命终止日期

然而,“某些情况”是否包括你自己的情况,由你自己决定。

*BakedGoods是由这里的这个家伙维护的:)

简单的解决方案!

<a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD">Click here</a>

Works in all Modern browsers.

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

在开始之前,有一个库来保存文件,库的名字是filesver .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>

现在复制这段代码,这段代码将下载一个带有Hello .txt文件的zip文件,其中包含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

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

我的第一步是从我保存的数据中生成二进制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();

试一试

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

如果你想下载二进制数据,请查看在这里

更新

2020.06.14我将Chrome升级到83.0及以上SO snippet停止工作(由于沙箱安全限制)-但JSFiddle版本工作- 在这里

当测试&;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);
        

}
对于像'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' });

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

你可以用它来保存文本和其他数据:

function downloadFile(name, data) {
let a = document.createElement("a");
if (typeof a.download !== "undefined") a.download = name;
a.href = URL.createObjectURL(new Blob([data], {
type: "application/octet-stream"
}));
a.dispatchEvent(new MouseEvent("click"));
}

这个函数将创建一个锚元素,通过.download (如果支持)设置名称,分配一个从对象(URL.createObjectURL)创建的url (.href),在本例中是对象,并分派一个单击事件。简而言之:就好像你在点击一个下载链接。

示例代码

downloadFile("textfile.txt", "A simple text file");
downloadFile(
"circle.svg",
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="42" />
</svg>`
);
downloadFile(
"utf8string.txt",
new Uint8Array([85, 84, 70, 45, 56, 32, 115, 116, 114, 105, 110, 103]) // "UTF-8 string"
);

这个函数也接受文件MediaSource:

function downloadFile(name, data) {
if (!(data instanceof File || data instanceof Blob || data instanceof MediaSource)) {
return downloadFile(name, new Blob([data], {
type: "application/octet-stream"
}));
}


let a = document.createElement("a");
if (typeof a.download !== "undefined") a.download = name;
a.href = URL.createObjectURL(data);
a.dispatchEvent(new MouseEvent("click"));
}

或者你可以使用两个函数:

function downloadFile(name, data) {
return downloadObject(new Blob([data], {
type: "application/octet-stream"
}));
}


function downloadObject(name, object) {
let a = document.createElement("a");
if (typeof a.download !== "undefined") a.download = name;
a.href = URL.createObjectURL(object);
a.dispatchEvent(new MouseEvent("click"));
}