Wie erstelle ich eine Datei im Speicher, die der Benutzer herunterladen kann, jedoch nicht über den Server?


824

Gibt es eine Möglichkeit, eine Textdatei auf der Clientseite zu erstellen und den Benutzer zum Herunterladen aufzufordern, ohne mit dem Server zu interagieren? Ich weiß, dass ich nicht direkt auf ihren Computer schreiben kann (Sicherheit und alles), aber kann ich sie erstellen und zum Speichern auffordern?


Antworten:


432

Sie können Daten-URIs verwenden. Die Browserunterstützung variiert. siehe Wikipedia . Beispiel:

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

Der Oktett-Stream soll eine Download-Eingabeaufforderung erzwingen. Andernfalls wird es wahrscheinlich im Browser geöffnet.

Für CSV können Sie Folgendes verwenden:

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

Probieren Sie die jsFiddle-Demo aus .


19
Dies ist keine browserübergreifende Lösung, aber definitiv einen Blick wert. Zum Beispiel beschränkt der IE die Unterstützung auf Daten-Uri. IE 8 begrenzt die Größe auf 32 KB und IE 7 und niedriger unterstützt überhaupt nicht.
Darin Dimitrov

8
In Chrome Version 19.0.1084.46 generiert diese Methode die folgende Warnung: "Ressource als Dokument interpretiert, aber mit MIME-Typ text / csv übertragen:" Daten: Text / csv, Feld1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". "" Ein Download wird nicht ausgelöst
Chris

2
Es funktioniert jetzt in Chrome (getestet gegen v20 und v21), aber nicht in IE9 (das ist vielleicht nur die jsFiddle, aber irgendwie bezweifle ich es).
Earcam

5
Der richtige Zeichensatz ist mit ziemlicher Sicherheit UTF-16, es sei denn, Sie haben Code, der ihn in UTF-8 konvertiert. JavaScript verwendet UTF-16 intern. Wenn Sie eine Text- oder CSV-Datei haben, beginnen Sie die Zeichenfolge mit '\ ufeff', dem Byte Order Mark für UTF-16BE, und Texteditoren können Nicht-ASCII-Zeichen korrekt lesen.
Larspars

14
Fügen Sie einfach das Attribut download = "txt.csv" hinzu, um den richtigen Dateinamen und die richtige Dateierweiterung zu erhalten und Ihrem Betriebssystem mitzuteilen, was damit zu tun ist.
Elshnkhll

724

Einfache Lösung für HTML5-fähige Browser ...

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>

Verwendungszweck

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

10
Ja. Genau das hat @MatthewFlaschen vor ungefähr 3 Jahren hier gepostet .
Joseph Silber

48
Ja, aber mit downloadAttribut können Sie den Dateinamen angeben ;-)
Matěj Pokorný


4
Chrome hängt die txtErweiterung nur an, wenn Sie im Dateinamen keine Erweiterung angeben . Wenn Sie dies tun, wird download("data.json", data)es wie erwartet funktionieren.
Carl Smith

6
Dies funktionierte bei mir in Chrome (73.0.3683.86) und Firefox (66.0.2). Es funktionierte NICHT in IE11 (11.379.17763.0) und Edge (44.17763.1.0).
Sam

231

Alle oben genannten Lösungen funktionierten nicht in allen Browsern. Folgendes funktioniert schließlich unter IE 10+, Firefox und Chrome (und ohne jQuery oder eine andere Bibliothek):

save: function(filename, data) {
    var blob = new Blob([data], {type: 'text/csv'});
    if(window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    }
    else{
        var elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;        
        document.body.appendChild(elem);
        elem.click();        
        document.body.removeChild(elem);
    }
}

Beachten Sie, dass Sie abhängig von Ihrer Situation möglicherweise auch URL.revokeObjectURL nach dem Entfernen aufrufen möchtenelem . Gemäß den Dokumenten für URL.createObjectURL :

Jedes Mal, wenn Sie createObjectURL () aufrufen, wird eine neue Objekt-URL erstellt, auch wenn Sie bereits eine für dasselbe Objekt erstellt haben. Jedes dieser Elemente muss durch Aufrufen von URL.revokeObjectURL () freigegeben werden, wenn Sie sie nicht mehr benötigen. Browser geben diese automatisch frei, wenn das Dokument entladen wird. Für eine optimale Leistung und Speichernutzung sollten Sie dies jedoch tun, wenn es sichere Zeiten gibt, in denen Sie diese explizit entladen können.


7
Tausend Dank. Ich habe alle hier aufgeführten Beispiele ausprobiert und nur dieses funktioniert mit jedem Browser. Dies sollte die akzeptierte Antwort sein.
LEM

In Chrome musste ich das Element nicht an den Körper anhängen, damit dies funktioniert.
JackMorrissey

1
Für AngularJS 1.x-Apps können Sie beim Erstellen ein Array von URLs erstellen und diese dann in der Funktion $ onDestroy der Komponente bereinigen. Das funktioniert großartig für mich.
Splaktar

1
Andere Antworten führten zu Failed: network errorin Chrome. Dieser funktioniert gut.
Wacholder

4
Dies funktionierte bei mir in Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) und Edge (44.17763.1.0).
Sam

185

Das obige Beispiel funktioniert in Chrome und IE einwandfrei, schlägt jedoch in Firefox fehl. Bitte denken Sie daran, einen Anker an den Körper anzuhängen und ihn nach dem Klicken zu entfernen.

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

4
Allerdings: Es gibt einen offenen Fehler in IE 10 (und ich habe ihn immer noch in 11 gesehen) , der "Zugriff verweigert" in die a.click()Zeile wirft , weil er glaubt, dass die Blob-URL einen Kreuzursprung hat.
Matt

@Matt data uri ist in einigen Browsern Cross-Origin. soweit ich weiß, nicht nur in msie, sondern auch in chrom. Sie können es testen, indem Sie versuchen, Javascript mit Daten-Uri zu injizieren. Es wird nicht in der Lage sein, auf andere Teile der Website
zuzugreifen

7
"Das obige Beispiel funktioniert in Chrome und IE einwandfrei, in Firefox jedoch nicht." Da sich die Reihenfolge der Antworten im Laufe der Zeit ändern kann, ist unklar, welche Antworten beim Schreiben über Ihren lagen. Können Sie genau angeben, welche Ansätze in Firefox nicht funktionieren?
Kevin

120

Ich benutze gerne FileSaver.js . Die Kompatibilität ist ziemlich gut (IE10 + und alles andere) und es ist sehr einfach zu bedienen:

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

Dies funktioniert hervorragend in Chrome. Wie erlaube ich dem Benutzer, den Speicherort der Datei auf der Festplatte anzugeben?
Gregm

6
Wow, danke für die einfach zu bedienende Bibliothek. Dies ist mit Sicherheit die beste Antwort, und wen interessieren heutzutage Leute, die HTML <5 verwenden?
notbad.jpeg

@gregm Ich bin nicht sicher, ob Sie mit diesem Plugin können.
Daniel Buckmaster

@gregm: Du meinst den Download-Speicherort? Dies hat nichts mit FileSaver.js zu tun. Sie müssen Ihre Browserkonfiguration so einstellen, dass vor jedem Download ein Ordner abgefragt wird, oder das ziemlich neue Download-Attribut für verwenden <a>.
CodeManX

1
Dies ist eine großartige Lösung für Browser der IE 10+ -Familie. IE unterstützt das Download-HTML-5-Tag noch nicht und die anderen Lösungen auf dieser Seite (und andere SO-Seiten, auf denen das gleiche Problem diskutiert wird) funktionierten einfach nicht für mich. FileSaver ftw!
TMc

22

Die folgende Methode funktioniert in IE11 +, Firefox 25+ und 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>

Sehen Sie dies in Aktion: http://jsfiddle.net/Kg7eA/

Firefox und Chrome unterstützen Daten-URIs für die Navigation, mit denen wir Dateien erstellen können, indem wir zu einem Daten-URI navigieren, während der IE dies aus Sicherheitsgründen nicht unterstützt.

Auf der anderen Seite verfügt der IE über eine API zum Speichern eines Blobs, mit der Dateien erstellt und heruntergeladen werden können.


Ich habe gerade jquery verwendet, um Ereignisse anzuhängen (onclick und onready) und Attribute festzulegen, was Sie auch mit Vanilla JS tun können. Der Kernteil (window.navigator.msSaveOrOpenBlob) benötigt keine Abfrage.
Dinesh Ygv

Es gibt immer noch die Größenbeschränkung für den Daten-Uri-Ansatz, nicht wahr?
Phil

msSaveOrOpenBlob wird hier als veraltet angezeigt: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat

13

Diese Lösung wird direkt aus dem Github-Repository von tiddlywiki (tiddlywiki.com) extrahiert. Ich habe Tiddlywiki in fast allen Browsern verwendet und es funktioniert wie ein Zauber:

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 Repo: Spar-Modul herunterladen


Es funktioniert sehr gut auf Chrome, aber nicht auf Firefox. Es erstellt eine Datei und lädt sie herunter, aber die Datei ist leer. Kein Inhalt. Irgendwelche Ideen warum? Nicht auf IE getestet ...
Narxx

2
außer dass die Funktion keinen Namen hat, ist dies mein Favorit
Yoraco Gonzales

11

Lösung, die auf IE10 funktioniert: (Ich brauchte eine CSV-Datei, aber es reicht aus, Typ und Dateiname in txt zu ändern.)

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

1
Ludovics Antwort beinhaltet dieses große Plus an Unterstützung für die anderen Browser.
Dan Dascalescu

10

Wenn Sie nur eine Zeichenfolge konvertieren möchten, die zum Herunterladen verfügbar ist, können Sie dies mit jQuery versuchen.

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

1
Scape-Daten mit encodeURI werden möglicherweise benötigt, wie ich hier vorgeschlagen habe, bevor ich einen Kommentar abgeben kann
atfornes

10

Das Paket js-file-download von github.com/kennethjiang/js-file-download behandelt Randfälle für die Browserunterstützung:

Zeigen Sie die Quelle an, um zu sehen, wie die auf dieser Seite genannten Techniken verwendet werden.

Installation

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

Verwendungszweck

import fileDownload from 'js-file-download'

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

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

1
Dank - gerade getestet - funktioniert mit Firefox, Chrome und Edge unter Windows
Brian Burns

8

Wie bereits erwähnt, ist filesaver ein großartiges Paket, um mit Dateien auf der Clientseite zu arbeiten. Bei großen Dateien ist dies jedoch nicht gut. StreamSaver.js ist eine alternative Lösung (auf die in FileServer.js verwiesen wird), die große Dateien verarbeiten kann:

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

1
Text Encoder ist derzeit sehr experimentell. Ich würde empfehlen, es zu vermeiden (oder zu füllen).
Brandon.Blanchard

7
var element = document.createElement('a');
element.setAttribute('href', 'data:text/text;charset=utf-8,' +      encodeURI(data));
element.setAttribute('download', "fileName.txt");
element.click();

1
Was sind die Unterschiede zwischen diesem Ansatz und dem Erstellen eines Blobs?
Dan Dascalescu


5

Basierend auf @ Rick Antwort, die wirklich hilfreich war.

Sie müssen die Zeichenfolge formen, datawenn Sie sie folgendermaßen freigeben möchten:

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

`Entschuldigung, ich kann die Antwort von @ Rick aufgrund meines derzeit schlechten Rufs in StackOverflow nicht kommentieren.

Ein Bearbeitungsvorschlag wurde geteilt und abgelehnt.


1
Ich konnte den Vorschlag nicht annehmen. Seltsam ... Ich habe den Code aktualisiert.
Rick

4

Wir können die URL- API, insbesondere URL.createObjectURL () , und die Blob- API verwenden, um so ziemlich alles zu codieren und herunterzuladen.

Wenn Ihr Download klein ist, funktioniert dies einwandfrei:

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 = ""


Wenn Ihr Download sehr umfangreich ist, können Sie anstelle des DOM ein Linkelement mit den Downloadparametern erstellen und einen Klick auslösen.

Beachten Sie, dass das Linkelement nicht an das Dokument angehängt ist, der Klick jedoch trotzdem funktioniert! Auf diese Weise können Sie einen Download von vielen hundert Mo erstellen.

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

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


Bonus! Laden Sie alle zyklischen Objekte herunter und vermeiden Sie die Fehler:

TypeError: zyklischer Objektwert (Firefox) TypeError: Konvertieren

Kreisstruktur zu JSON (Chrome und Opera) TypeError: Circular

Referenz im Wertargument nicht unterstützt (Edge)

Verwenden von https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

In diesem Beispiel wird das documentObjekt als json heruntergeladen .

/* 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()



2

Diese Funktion funktioniert.

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

Können Sie die Datei in einem neuen Tab öffnen, wobei der Dateiname zugewiesen bleibt, aber nicht heruntergeladen wird, sondern nur in einem Tab geöffnet wird?
Carl Kroeger Ihl

1

Die folgende Methode funktioniert in IE10 +, Edge, Opera, FF und 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);
    }
}

Rufen Sie einfach die Funktion auf:

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

0

Für mich hat das perfekt funktioniert, mit dem gleichen Dateinamen und der gleichen Erweiterung, die heruntergeladen wurden

<a href={"data:application/octet-stream;charset=utf-16le;base64," + file64 } download={title} >{title}</a>

‚title‘ ist der Dateiname mit der Erweiterung das heißt sample.pdf, waterfall.jpgetc ..

'file64' ist der base64-Inhalt wie dieser, dh Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO


0

Ich würde ein <a></a>Tag verwenden und dann das setzen href='path'. Platzieren Sie anschließend ein Bild zwischen den <a>Elementen, damit ich es visuell sehen kann. Wenn Sie möchten, können Sie eine Funktion erstellen, die das ändert, hrefsodass es nicht nur derselbe Link ist, sondern auch dynamisch.

Geben Sie dem <a>Tag auch ein, idwenn Sie mit Javascript darauf zugreifen möchten.

Beginnend mit der HTML-Version:

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

Jetzt mit 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");
          }
}

-22

Wenn die Datei Textdaten enthält, verwende ich eine Technik, die darin besteht, den Text in ein Textbereichselement einzufügen und ihn vom Benutzer auswählen zu lassen (in den Textbereich klicken und dann Strg-A drücken), dann zu kopieren und anschließend in einen Texteditor einzufügen.


30
Ich hatte das in Betracht gezogen, aber aus benutzerfreundlicher Sicht ist dies katastrophal. Außerdem muss die Datei mit einer CSV-Erweiterung gespeichert werden. Versuchen Sie, dies Ihren Benutzern mitzuteilen.
Joseph Silber
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.