Come creare un file in memoria che l'utente può scaricare, ma non attraverso il server?


Risposte:


432

È possibile utilizzare URI di dati. Il supporto del browser varia; vedi Wikipedia . Esempio:

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

Il flusso di ottetti è quello di forzare un prompt di download. Altrimenti, probabilmente si aprirà nel browser.

Per CSV, puoi utilizzare:

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

Prova la demo di jsFiddle .


19
Questa non è una soluzione cross browser ma sicuramente qualcosa che vale la pena guardare. Ad esempio IE limita il supporto ai dati uri. IE 8 limita le dimensioni a 32 KB e IE 7 e versioni precedenti non supportano affatto.
Darin Dimitrov,

8
nella versione 19.0.1084.46 di Chrome, questo metodo genera il seguente avviso: "Risorsa interpretata come Documento ma trasferita con il tipo MIME text / csv:" dati: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " Un download non viene attivato
Chris,

2
Funziona ora in Chrome (testato contro v20 e v21) ma non IE9 (potrebbe essere solo il jsFiddle, ma in qualche modo ne dubito).
earcam

5
Il set di caratteri corretto è quasi certamente UTF-16, a meno che non si disponga di un codice per convertirlo in UTF-8. JavaScript utilizza UTF-16 internamente. Se hai un file di testo o CSV, avvia la stringa con '\ ufeff', il Byte Order Mark per UTF-16BE e gli editor di testo saranno in grado di leggere correttamente i caratteri non ASCII.
Larspars,

14
Aggiungi l'attributo download = "txt.csv" per avere il nome e l'estensione del file corretti e dire al tuo sistema operativo cosa farne.
elshnkhll,

725

Soluzione semplice per browser predisposti per 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>

uso

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

10
Sì. Questo è esattamente ciò che @MatthewFlaschen ha pubblicato qui circa 3 anni fa .
Joseph Silber,

48
Sì, ma con l' downloadattributo puoi specificare il nome del file ;-)
Matěj Pokorný

2
Come @earcam ha già sottolineato nei commenti sopra .
Joseph Silber,

4
Chrome aggiunge l' txtestensione solo se non si specifica un'estensione nel nome file. Se lo fai download("data.json", data)funzionerà come previsto.
Carl Smith,

6
Questo ha funzionato per me in Chrome (73.0.3683.86) e Firefox (66.0.2). Lo ha fatto NON lavorare in IE11 (11.379.17763.0) e Edge (44.17763.1.0).
Sam

231

Tutte le soluzioni di cui sopra non funzionavano in tutti i browser. Ecco cosa finalmente funziona su IE 10+, Firefox e Chrome (e senza jQuery o qualsiasi altra libreria):

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

Tieni presente che, a seconda della situazione, potresti anche voler chiamare URL.revokeObjectURL dopo averlo rimosso elem. Secondo i documenti per URL.createObjectURL :

Ogni volta che chiami createObjectURL (), viene creato un nuovo URL oggetto, anche se ne hai già creato uno per lo stesso oggetto. Ognuno di questi deve essere rilasciato chiamando URL.revokeObjectURL () quando non ne hai più bisogno. I browser li rilasceranno automaticamente quando il documento viene scaricato; tuttavia, per prestazioni ottimali e utilizzo della memoria, se ci sono momenti sicuri in cui è possibile scaricarli esplicitamente, è necessario farlo.


7
Grazie mille. Ho provato tutti gli esempi elencati qui e solo questo funziona con qualsiasi browser. Questa dovrebbe essere la risposta accettata.
LEM,

In Chrome, in realtà non ho dovuto aggiungere l'elemento al corpo per farlo funzionare.
JackMorrissey,

1
Per le app AngularJS 1.x, puoi creare un array di URL mentre vengono creati e quindi ripulirli nella funzione $ onDestroy del componente. Funziona benissimo per me.
Splaktar,

1
Altre risposte hanno portato a Failed: network errorin Chrome. Questo funziona bene.
Ginepro

4
Questo ha funzionato per me in Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) e Edge (44.17763.1.0).
Sam

185

Tutti gli esempi sopra riportati funzionano bene in Chrome e IE, ma falliscono in Firefox. Si prega di considerare di aggiungere un ancoraggio al corpo e rimuoverlo dopo il clic.

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
Tuttavia: esiste un bug aperto in IE 10 (e l'ho ancora visto in 11) che genera "Accesso negato" sulla a.click()linea perché pensa che l'URL del BLOB sia di origine incrociata.
Matt,

@Matt data uri è origine incrociata in alcuni browser. per quanto ne so, non solo in msie, ma anche in Chrome. puoi provarlo provando a iniettare javascript con dati uri. Non sarà in grado di accedere ad altre parti del sito ...
inf3rno,

7
"Tutti gli esempi sopra riportati funzionano bene su Chrome e IE, ma non funzionano su Firefox." Poiché l'ordine delle risposte può cambiare nel tempo, non è chiaro quali risposte siano state al di sopra delle tue quando hai scritto questo. Puoi indicare esattamente quali approcci non funzionano in Firefox?
Kevin,

120

Sto usando felicemente FileSaver.js . La sua compatibilità è piuttosto buona (IE10 + e tutto il resto) ed è molto semplice da usare:

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

Funziona alla grande su Chrome. Come posso consentire all'utente di specificare la posizione del file sul disco?
Greg

6
Caspita, grazie per la libreria facile da usare. Questa è facilmente la risposta migliore e chi se ne frega delle persone che usano HTML <5 in questi giorni in qualche modo?
notbad.jpeg,

@gregm Non sono sicuro che puoi farlo con questo plugin.
Daniel Buckmaster,

@gregm: intendi la posizione del download? Non è correlato a FileSaver.js, è necessario impostare la configurazione del browser in modo che richieda una cartella prima di ogni download o utilizzare l' attributo di download piuttosto nuovo su <a>.
CodeManX,

1
Questa è un'ottima soluzione per la famiglia di browser IE 10+. Internet Explorer non supporta ancora il tag HTML 5 per il download e le altre soluzioni in questa pagina (e altre pagine SO che parlano dello stesso problema) semplicemente non funzionavano per me. FileSaver ftw!
TMc,

22

Il seguente metodo funziona in IE11 +, Firefox 25+ e 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>

Vedi questo in Azione: http://jsfiddle.net/Kg7eA/

Firefox e Chrome supportano l'URI dei dati per la navigazione, che ci consente di creare file navigando verso un URI dei dati, mentre IE non lo supporta per motivi di sicurezza.

D'altro canto, IE ha API per il salvataggio di un BLOB, che può essere utilizzato per creare e scaricare file.


Ho appena usato jquery per allegare eventi (onclick e già) e impostare attributi, cosa che puoi fare anche con JS vaniglia. La parte principale (window.navigator.msSaveOrOpenBlob) non necessita di jquery.
Dinesh YGV,

C'è ancora la limitazione delle dimensioni per l'approccio uri dati, no?
Phil,

msSaveOrOpenBlob è mostrato come obsoleto qui: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat

13

Questa soluzione viene estratta direttamente dal repository github di tiddlywiki (tiddlywiki.com). Ho usato tiddlywiki in quasi tutti i browser e funziona come un fascino:

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

Repository Github: scarica il modulo saver


Funziona molto bene su Chrome, ma non su Firefox. Crea un file e lo scarica, ma il file è vuoto. Nessun contenuto. Qualche idea sul perché? Non ho ancora testato su IE ...
Narxx,

2
tranne che la funzione non ha un nome, questo è il mio preferito
Yoraco Gonzales

11

Soluzione che funziona su IE10: (avevo bisogno di un file CSV, ma è sufficiente cambiare tipo e nome file in 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")

1
La risposta di Ludovic include questo grande, oltre al supporto per gli altri browser.
Dan Dascalescu,

10

Se vuoi solo convertire una stringa per renderla disponibile per il download, puoi provare usando jQuery.

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

1
Dati scape con encodeURI potrebbero essere necessari come ho suggerito qui prima di essere in grado di commento: stackoverflow.com/a/32441536/4928558
atfornes

10

Il pacchetto js-file-download da github.com/kennethjiang/js-file-download gestisce i casi limite per il supporto del browser:

Visualizza sorgente per vedere come utilizza le tecniche menzionate in questa pagina.

Installazione

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

uso

import fileDownload from 'js-file-download'

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

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

1
Grazie - appena testato - funziona con Firefox, Chrome e Edge su Windows
Brian Burns,

8

Come accennato in precedenza, filesaver è un ottimo pacchetto per lavorare con i file sul lato client. Ma non funziona bene con file di grandi dimensioni. StreamSaver.js è una soluzione alternativa (indicata in FileServer.js) in grado di gestire file di grandi dimensioni:

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 è altamente sperimentale in questo momento, suggerirei di evitarlo (o di riempirlo con polifilm).
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
Quali sono le differenze tra questo approccio e la creazione di un BLOB?
Dan Dascalescu,


5

Basato sulla risposta di @Rick che è stato davvero utile.

Devi scapezzare la stringa datase vuoi condividerla in questo modo:

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

`Mi dispiace non posso commentare la risposta di @ Rick a causa della mia attuale scarsa reputazione in StackOverflow.

Un suggerimento di modifica è stato condiviso e rifiutato.


1
Non sono stato in grado di accettare il suggerimento. Strano ... ho aggiornato il codice.
Rick,

4

Siamo in grado di utilizzare l' URL di api, in particolare URL.createObjectURL () , e il Blob API per codificare e scaricare praticamente qualsiasi cosa.

Se il download è piccolo, funziona bene:

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


Se il download è enorme, invece di utilizzare il DOM, un modo migliore è quello di creare un elemento di collegamento con i parametri di download e attivare un clic.

Notare che l'elemento del collegamento non è stato aggiunto al documento ma il clic funziona comunque! Questo è possibile per creare un download di molte centinaia di Mo in questo modo.

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! Scarica eventuali oggetti ciclici , evita gli errori:

TypeError: valore oggetto ciclico (Firefox) TypeError: conversione

struttura circolare a JSON (Chrome e Opera) TypeError: Circular

riferimento nell'argomento valore non supportato (Edge)

Utilizzo di https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

In questo esempio, scaricare l' documentoggetto come 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+"]")})}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

Questa funzione di seguito ha funzionato.

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

puoi aprire il file in una nuova scheda mantenendo il fileName assegnato, ma non il download, semplicemente aprendo in una scheda?
Carl Kroeger Ihl,

1

Il seguente metodo funziona in IE10 +, Edge, Opera, FF e 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);
    }
}

Quindi, basta chiamare la funzione:

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

0

Per me questo ha funzionato perfettamente, con lo stesso nome file ed estensione scaricati

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

'title' è il nome del file con l'estensione cioè sample.pdf,waterfall.jpg ecc ..

'file64' è il contenuto base64 qualcosa del genere cioè Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO


0

Vorrei usare un <a></a>tag quindi impostare il href='path'. Successivamente, posiziona un'immagine tra gli <a>elementi in modo che io possa avere un elemento visivo per vederlo. Se lo desideri, puoi creare una funzione che cambierà ilhref modo che non sia solo lo stesso link ma sia dinamica.

Dai anche <a>un tag idse vuoi accedervi con javascript.

A partire dalla versione HTML:

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

Ora con 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

Se il file contiene dati di testo, una tecnica che uso è quella di mettere il testo in un elemento textarea e fare in modo che l'utente lo selezioni (fai clic in textarea quindi ctrl-A), quindi copia seguito da un incolla in un editor di testo.


30
Lo avevo considerato, ma dal punto di vista della facilità d'uso, questo è disastroso. Inoltre, il file deve essere salvato con un'estensione CSV. Prova a dirlo ai tuoi utenti.
Joseph Silber,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.