从 Blob 中保存到本地文件

45

我有一个棘手的问题要问你,我已经苦恼了一段时间。

我正在寻找一种解决方案,在没有本地存储(因为本地存储有5MB限制)的情况下将文件保存到用户计算机上。 我想要“保存到文件”对话框,但是我要保存的数据仅在javascript中可用,并且我想防止将数据发送回服务器,然后再次发送它。

使用场景是,我正在开发的服务正在保存压缩和加密的用户数据块,因此服务器不知道这些块中的内容,并且通过将数据发送回服务器,这将导致4倍的流量,并且服务器正在接收未加密的数据,这将使整个加密无效。

我找到了一种JavaScript函数来使用“保存到文件”对话框将数据保存到用户计算机中,但是这项工作已停止并且没有得到充分支持。就是这个: http://www.w3.org/TR/file-writer-api/

那么,既然我没有window.saveAs,从Blob对象中保存数据而不将所有内容发送到服务器的方法是什么?

如果我能得到提示,该怎么搜索就太好了。

我知道这行得通,因为MEGA正在使用它,但我想要自己的解决方案 :)


1
类似的编程问题已经在这里发布了 - https://dev59.com/OWYr5IYBdhLWcg3wxM-8 和这里 - https://dev59.com/C2Ml5IYBdhLWcg3wHTug - Indu Devanath
5个回答

69

你最好使用Blob URL(这是一种指向浏览器内存中对象的特殊URL):

var myBlob = ...;
var blobUrl = URL.createObjectURL(myBlob);

现在你有两种选择,一种是简单地重定向到这个url(window.location.replace(blobUrl)),另一种是创建一个链接。第二种解决方案允许你指定一个默认文件名:

var link = document.createElement("a"); // Or maybe get it from the current document
link.href = blobUrl;
link.download = "aDefaultFileName.txt";
link.innerHTML = "Click here to download the file";
document.body.appendChild(link); // Or append it whereever you want

2
不要忘记通过调用URL.revokeObjectURL()释放内存。 - ihor.eth

9

FileSaver.js 实现了 saveAs 功能,用于在某些不支持该功能的浏览器上使用

https://github.com/eligrey/FileSaver.js

已测试 FileSaver.js 1.3.8 在 Chromium 75 和 Firefox 68 上正常工作,但这两款浏览器都不支持 saveAs 功能。

其工作原理似乎是通过 创建一个元素,并使用 JavaScript 单击它来实现。Web 的恐怖之处就在于此。

下面是一个演示示例,它会将使用 canvas.toBlob 生成的 Blob 对象以所选名称 mypng.png 保存到您的下载文件夹中:

var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
var pixel_size = 1;

function draw() {
    console.log("draw");
    for (x = 0; x < canvas.width; x += pixel_size) {
        for (y = 0; y < canvas.height; y += pixel_size) {
            var b = 0.5;
            ctx.fillStyle =
                "rgba(" +
                (x / canvas.width) * 255 + "," +
                (y / canvas.height) * 255 + "," +
                b * 255 +
                ",255)"
            ;
            ctx.fillRect(x, y, pixel_size, pixel_size);
        }
    }
    canvas.toBlob(function(blob) {
      saveAs(blob, 'mypng.png');
    });
}
window.requestAnimationFrame(draw);
<canvas id="my-canvas" width="512" height="512" style="border:1px solid black;"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js"></script>

以下是下载多个图像的动画版本:将HTML5 Canvas序列转换为视频文件

另请参见:


1
有没有办法在每次保存之前避免被提示? - Michael
另外,我如何知道保存已完成?现在我的代码甚至在我按下保存键之前就继续生成新的帧,但我需要每个帧都被保存。 - Michael
1
@Michael 你好,我在链接的答案中已经部分解决了这些问题:https://dev59.com/J2Ik5IYBdhLWcg3we-L5#57153718 摘要:在保存前提示:我无法避免第一个。等待帧保存:似乎不可能,我只能降低 FPS 并期望最好的结果,但这通常是无法接受的。 - Ciro Santilli OurBigBook.com

4

这里是直接方式。


canvas.toBlob(function(blob){

    console.log(typeof(blob)) //let you have 'blob' here

    var blobUrl = URL.createObjectURL(blob);
    
    var link = document.createElement("a"); // Or maybe get it from the current document
    link.href = blobUrl;
    link.download = "image.jpg";
    link.innerHTML = "Click here to download the file";

    document.body.appendChild(link); // Or append it whereever you want
    document.querySelector('a').click() //can add an id to be specific if multiple anchor tag, and use #id

  }, 'image/jpeg', 1); // JPEG at 100% quality

花了一些时间才想到这个解决方法,如果有帮助请留言。 感谢Sebastien C的回答。

你能不能调用link.click()而不是document.querySelector('a').click()?如果这样可以的话,那么你就不需要查找相应的a标签,而是直接使用你已经有的引用,这样你甚至都不需要为该标签设置一个ID。 - Adam L. S.

0
这个节点依赖更多的是utils fs-web;
npm i fs-web

使用方法

import * as fs from 'fs-web';

async processFetch(url, file_path = 'cache-web') {
    const fileName = `${file_path}/${url.split('/').reverse()[0]}`;
    let cache_blob: Blob;
    await fs.readString(fileName).then((blob) => {
      cache_blob = blob;
    }).catch(() => { });
    if (!!cache_blob) {
      this.prepareBlob(cache_blob);
      console.log('FROM CACHE');
    } else {
      await fetch(url, {
        headers: {},
      }).then((response: any) => {
        return response.blob();
      }).then((blob: Blob) => {
        fs.writeFile(fileName, blob).then(() => {
          return fs.readString(fileName);
        });
        this.prepareBlob(blob);
      });
 }

}


-3

从文件选择器或input type=file文件选择器中,将文件名保存到本地存储:

HTML:

<audio id="player1">Your browser does not support the audio element</audio>

JavaScript:

function picksinglefile() {
    var fop = new Windows.Storage.Pickers.FileOpenPicker();
    fop.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.musicLibrary;
    fop.fileTypeFilter.replaceAll([".mp3", ".wav"]);
    fop.pickSingleFileAsync().then(function (file) {
        if (file) {
            // save the file name to local storage
            localStorage.setItem("alarmname$", file.name.toString());
        } else {
            alert("Operation Cancelled");
        }
    });
}

然后在您的代码中稍后,当您想要播放所选文件时,请使用以下代码,它仅通过音乐库中的名称获取文件。(在UWP包清单中,将您的“功能”设置为包括“音乐库”)。

        var l = Windows.Storage.KnownFolders.musicLibrary;
        var f = localStorage.getItem("alarmname$").toString(); // retrieve file by name
        l.getFileAsync(f).then(function (file) {
            // storagefile file is available, create URL from it
            var s = window.URL.createObjectURL(file);
            var x = document.getElementById("player1");
            x.setAttribute("src", s);
            x.play();
        });

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