Jak utworzyć plik w pamięci, aby użytkownik mógł go pobrać, ale nie przez serwer?

823

Czy jest jakiś sposób, aby utworzyć plik tekstowy po stronie klienta i poprosić użytkownika o pobranie go bez interakcji z serwerem? Wiem, że nie mogę pisać bezpośrednio na ich komputer (bezpieczeństwo i wszystko), ale czy mogę je utworzyć i poprosić o zapisanie?

Joseph Silber
źródło
1
Zobacz także: JavaScript: Utwórz i zapisz plik
Henry Woody

Odpowiedzi:

433

Możesz użyć URI danych. Obsługa przeglądarki jest różna; patrz Wikipedia . Przykład:

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

Strumień oktetów wymusza monit o pobranie. W przeciwnym razie prawdopodobnie otworzy się w przeglądarce.

W przypadku CSV możesz użyć:

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

Wypróbuj wersję demo jsFiddle .

Matthew Flaschen
źródło
19
To nie jest rozwiązanie dla wielu przeglądarek, ale zdecydowanie coś, na co warto spojrzeć. Na przykład IE ogranicza obsługę danych uri. IE 8 ogranicza rozmiar do 32 KB, a IE 7 i niższe nie obsługuje wcale.
Darin Dimitrov
8
w wersji Chrome 19.0.1084.46 ta metoda generuje następujące ostrzeżenie: „Zasób zinterpretowany jako dokument, ale przesłany z typem MIME text / csv:” data: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0go% 2Cai% 0A ”. „ Pobieranie nie jest uruchamiane
Chris
2
Działa teraz w Chrome (testowany przeciwko v20 i v21), ale nie w IE9 (może to być tylko jsFiddle, ale jakoś w to wątpię).
kamera nauszna
5
Prawidłowy zestaw znaków to prawie na pewno UTF-16, chyba że masz kod konwertujący go na UTF-8. JavaScript używa UTF-16 wewnętrznie. Jeśli masz plik tekstowy lub CSV, rozpocznij ciąg od „\ ufeff”, znaku kolejności bajtów dla UTF-16BE, a edytory tekstu będą w stanie poprawnie odczytać znaki spoza ASCII.
larspars
14
Wystarczy dodać atrybut download = "txt.csv", aby mieć prawidłową nazwę pliku i rozszerzenie oraz poinformować system operacyjny, co z tym zrobić.
elshnkhll
724

Proste rozwiązanie dla przeglądarek obsługujących 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>

Stosowanie

download('test.txt', 'Hello world!');
Matěj Pokorný
źródło
10
Tak. To jest dokładnie to, co @MatthewFlaschen opublikował tutaj około 3 lata temu .
Joseph Silber,
48
Tak, ale za pomocą downloadatrybutu można określić nazwę pliku ;-)
Matěj Pokorný
2
Jak @earcam już wskazał w komentarzach powyżej .
Joseph Silber,
4
Chrome dołącza txtrozszerzenie tylko wtedy, gdy nie podasz rozszerzenia w nazwie pliku. Jeśli to zrobisz download("data.json", data), będzie działać zgodnie z oczekiwaniami.
Carl Smith,
6
Działa to dla mnie w Chrome (73.0.3683.86) i Firefox (66.0.2). To było nie pracować w IE11 (11.379.17763.0) i krawędź (44.17763.1.0).
Sam
231

Wszystkie powyższe rozwiązania nie działały we wszystkich przeglądarkach. Oto, co w końcu działa w IE 10+, Firefox i Chrome (i bez jQuery lub innej biblioteki):

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

Pamiętaj, że w zależności od sytuacji możesz również chcieć wywołać URL.revokeObjectURL po usunięciu elem. Według dokumentacji dla URL.createObjectURL :

Za każdym razem, gdy wywołujesz funkcję createObjectURL (), tworzony jest nowy adres URL obiektu, nawet jeśli już utworzyłeś go dla tego samego obiektu. Każdy z nich musi zostać zwolniony poprzez wywołanie URL.revokeObjectURL (), kiedy już ich nie potrzebujesz. Przeglądarki zwolnią je automatycznie po rozładowaniu dokumentu; jednak w celu uzyskania optymalnej wydajności i wykorzystania pamięci, jeśli istnieją bezpieczne czasy, kiedy można je jawnie rozładować, należy to zrobić.

Ludovic Feltz
źródło
7
Stukrotne dzięki. Wypróbowałem wszystkie wymienione tutaj przykłady i tylko ten działa z każdą przeglądarką. To powinna być zaakceptowana odpowiedź.
LEM
W Chrome tak naprawdę nie musiałem dołączać elementu do ciała, aby to zadziałało.
JackMorrissey,
1
W przypadku aplikacji AngularJS 1.x można zbudować tablicę adresów URL podczas ich tworzenia, a następnie wyczyścić je w funkcji $ onDestroy komponentu. To działa świetnie dla mnie.
Splaktar
1
Inne odpowiedzi doprowadziły Failed: network errorw Chrome. Ten działa dobrze.
jałowiec
4
Działa to dla mnie w Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) i Edge (44.17763.1.0).
Sam
185

Wszystkie powyższe przykłady działają dobrze w Chrome i IE, ale nie działają w Firefoksie. Rozważ dołączenie kotwicy do ciała i usunięcie jej po kliknięciu.

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);
naren
źródło
4
Jednak: w IE 10 jest otwarty błąd (i wciąż widziałem go w 11), który wyświetla w a.click()wierszu „Odmowa dostępu”, ponieważ uważa, że ​​adres URL obiektu blob to cross-origin.
Matt
@Matt Uri data jest cross origin w niektórych przeglądarkach. o ile wiem, nie tylko w msie, ale także w chrome. możesz to przetestować, próbując wstrzyknąć javascript z danymi URI. Nie będzie miał dostępu do innych części strony ...
inf3rno,
7
„Wszystkie powyższe przykłady działają dobrze w Chrome i IE, ale nie działają w Firefox.” Ponieważ kolejność odpowiedzi może się zmieniać w czasie, nie jest jasne, które odpowiedzi były wyższe niż twoje, kiedy to napisałeś. Czy możesz dokładnie wskazać, które podejścia nie działają w przeglądarce Firefox?
Kevin,
120

Z przyjemnością korzystam z FileSaver.js . Jego kompatybilność jest całkiem dobra (IE10 + i wszystko inne) i jest bardzo prosta w użyciu:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");
Daniel Buckmaster
źródło
Działa to świetnie w Chrome. Jak zezwolić użytkownikowi na określenie lokalizacji pliku na dysku?
gregm
6
Wow, dzięki za łatwą w użyciu bibliotekę. To jest z pewnością najlepsza odpowiedź, a kogo obchodzą osoby używające HTML <5 w dzisiejszych czasach w jakikolwiek sposób?
notbad.jpeg
@gregm Nie jestem pewien, czy możesz z tą wtyczką.
Daniel Buckmaster
@gregm: Masz na myśli lokalizację pobierania? To nie jest związane z FileSaver.js, trzeba ustawić konfigurację przeglądarki tak, że prosi o folderze przed każdym pobrania lub użyj raczej nowy pobierania atrybutu na <a>.
CodeManX
1
To jest świetne rozwiązanie dla rodziny przeglądarek IE 10+. IE nie obsługuje jeszcze tagu pobierania HTML 5, a inne rozwiązania na tej stronie (i innych stronach SO omawiających ten sam problem) po prostu nie działały dla mnie. FileSaver ftw!
TMc
22

Poniższa metoda działa w IE11 +, Firefox 25+ i 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>

Zobacz to w akcji: http://jsfiddle.net/Kg7eA/

Firefox i Chrome obsługują URI danych do nawigacji, co pozwala nam tworzyć pliki, przechodząc do identyfikatora URI danych, podczas gdy IE nie obsługuje go ze względów bezpieczeństwa.

Z drugiej strony IE ma interfejs API do zapisywania obiektu blob, którego można używać do tworzenia i pobierania plików.

dinesh ygv
źródło
Właśnie użyłem jquery do dołączania zdarzeń (onclick i onready) i ustawiania atrybutów, które można również zrobić z waniliowym JS. Część rdzeń (window.navigator.msSaveOrOpenBlob) nie potrzebuje jquery.
dinesh ygv
Wciąż istnieje ograniczenie rozmiaru dla metody URI danych, prawda?
phil
msSaveOrOpenBlob jest pokazany jako przestarzały tutaj: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat
13

To rozwiązanie jest pobierane bezpośrednio z repozytorium github tiddlywiki (tiddlywiki.com). Użyłem tiddlywiki w prawie wszystkich przeglądarkach i działa jak urok:

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

Repozytorium Github: Pobierz moduł wygaszacza

Danielo515
źródło
Działa bardzo ładnie w Chrome, ale nie w Firefox. Tworzy plik i pobiera go, ale plik jest pusty. Brak zawartości. Jakieś pomysły dlaczego? Nie testowałem na IE ...
Narxx
2
poza tym, że funkcja nie ma nazwy, jest to moja ulubiona
Yoraco Gonzales,
11

Rozwiązanie, które działa na IE10: (Potrzebowałem pliku csv, ale wystarczy zmienić typ i nazwę pliku na 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")
Dzarek
źródło
1
Odpowiedź Ludovica obejmuje to duże plus wsparcie dla innych przeglądarek.
Dan Dascalescu,
10

Jeśli chcesz tylko przekonwertować ciąg, aby był dostępny do pobrania, możesz wypróbować to za pomocą jQuery.

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));
Stóg
źródło
1
Scape danych z encodeURI może być potrzebny, tak jak zasugerowałem tutaj, zanim będzie można komentować: stackoverflow.com/a/32441536/4928558
atfornes
10

Pakiet pobierania pliku js ze strony github.com/kennethjiang/js-file-download obsługuje skrzynki na krawędziach do obsługi przeglądarki:

Wyświetl źródło, aby zobaczyć, w jaki sposób wykorzystuje techniki wymienione na tej stronie.

Instalacja

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

Stosowanie

import fileDownload from 'js-file-download'

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

fileDownload(data, 'filename.csv', 'text/csv')
Beau Smith
źródło
1
Dzięki - właśnie przetestowane - współpracuje z Firefox, Chrome i Edge w systemie Windows
Brian Burns,
8

Jak wspomniano wcześniej, Fileaver to świetny pakiet do pracy z plikami po stronie klienta. Ale nie radzi sobie dobrze z dużymi plikami. StreamSaver.js to alternatywne rozwiązanie (wskazane w FileServer.js), które może obsługiwać duże pliki:

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()
Mostafa
źródło
1
Text Encoder jest w tej chwili wysoce eksperymentalny, sugerowałbym unikanie go (lub wielokrotne wypełnianie).
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();
MANVENDRA LODHI
źródło
1
Jakie są różnice między tym podejściem a tworzeniem obiektu Blob?
Dan Dascalescu,
5

Na podstawie odpowiedzi @Rick, która była bardzo pomocna.

Musisz scape'ować ciąg, datajeśli chcesz go udostępnić w ten sposób:

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

`Niestety nie mogę skomentować odpowiedzi @ Ricka z powodu mojej obecnej niskiej reputacji w StackOverflow.

Edit sugestia była wspólna i odrzucone.

atfornes
źródło
1
Nie mogłem zaakceptować sugestii. Dziwne ... Zaktualizowałem kod.
Rick
4

Możemy użyć interfejsu API URL , w szczególności URL.createObjectURL () oraz interfejsu API Blob do kodowania i pobierania praktycznie wszystkiego.

Jeśli pobieranie jest małe, działa to dobrze:

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


Jeśli pobieranie jest ogromne, zamiast korzystania z DOM, lepszym sposobem jest utworzenie elementu łącza z parametrami pobierania i uruchomienie kliknięcia.

Zwróć uwagę, że element łącza nie jest dołączany do dokumentu, ale i tak kliknięcie działa! W ten sposób można utworzyć pobieranie setek Mo.

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>


Premia! Pobierz dowolne obiekty cykliczne , unikaj błędów:

TypeError: cykliczna wartość obiektu (Firefox) TypeError: Converting

struktura kołowa do JSON (Chrome i Opera) TypeError: Circular

nieobsługiwany argument odwołania do wartości (Edge)

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

W tym przykładzie pobranie documentobiektu jako 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()

NVRM
źródło
2

Ta funkcja poniżej działała.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }
Giapnh
źródło
czy możesz otworzyć plik na nowej karcie, zachowując przypisany plikNazwa, ale nie pobieranie, otwierając tylko na karcie?
Carl Kroeger Ihl,
1

Następująca metoda działa w IE10 +, Edge, Opera, FF i 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);
    }
}

Więc po prostu wywołaj funkcję:

saveDownloadedData('test.txt', 'Lorem ipsum');
Denys Rusov
źródło
0

Dla mnie działało to idealnie, przy pobieraniu tej samej nazwy pliku i rozszerzenia

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

„title” to nazwa pliku z rozszerzeniem np: sample.pdf, waterfall.jpgitp ..

„file64” to treść base64 coś takiego, tj. Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO

abdul
źródło
0

Chciałbym użyć <a></a>tagu, a następnie ustawić href='path'. Następnie umieść obraz pomiędzy <a>elementami, aby móc go zobaczyć. Jeśli chcesz, możesz utworzyć funkcję, która to zmieni, hreftak aby nie był to tylko ten sam link, ale był dynamiczny.

Daj <a>tagowi idrównież, jeśli chcesz uzyskać do niego dostęp za pomocą javascript.

Począwszy od wersji 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>

Teraz z 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");
          }
}
darrell
źródło
-22

Jeśli plik zawiera dane tekstowe, stosuję technikę umieszczania tekstu w elemencie textarea i niech użytkownik go wybierze (kliknij obszar tekstowy, a następnie Ctrl-A), a następnie skopiuj, a następnie wklej do edytora tekstu.

HBP
źródło
30
Rozważyłem to, ale z punktu widzenia łatwości obsługi jest to katastrofalne. Ponadto plik musi zostać zapisany z rozszerzeniem CSV. Spróbuj to powiedzieć swoim użytkownikom.
Joseph Silber,