Bagaimana cara membuat file di memori untuk diunduh pengguna, tetapi tidak melalui server?

826

Apakah ada cara saya dapat membuat file teks di sisi klien dan meminta pengguna untuk mengunduhnya, tanpa interaksi dengan server? Saya tahu saya tidak bisa menulis langsung ke mesin mereka (keamanan dan semuanya), tetapi bisakah saya membuat dan meminta mereka untuk menyimpannya?

Joseph Silber
sumber
1
Lihat juga: JavaScript: Buat dan simpan file
Henry Woody

Jawaban:

432

Anda dapat menggunakan URI data. Dukungan browser bervariasi; lihat Wikipedia . Contoh:

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

Oktet-stream adalah untuk memaksa prompt pengunduhan. Kalau tidak, mungkin akan terbuka di browser.

Untuk CSV, Anda dapat menggunakan:

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

Coba demo jsFiddle .

Matthew Flaschen
sumber
19
Ini bukan solusi lintas browser tetapi pasti sesuatu yang layak untuk dilihat. Misalnya IE membatasi dukungan untuk data uri. IE 8 membatasi ukuran menjadi 32KB dan IE 7 dan lebih rendah tidak mendukung sama sekali.
Darin Dimitrov
8
di Chrome Versi 19.0.1084.46, metode ini menghasilkan peringatan berikut: "Sumber daya diartikan sebagai Dokumen tetapi ditransfer dengan teks tipe MIME / csv:" data: teks / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " Pengunduhan tidak dipicu
Chris
2
Itu tidak bekerja di Chrome sekarang (diuji terhadap v20 dan v21) tetapi tidak IE9 (yang mungkin saja jsFiddle, tapi entah bagaimana saya meragukannya).
earcam
5
Charset yang benar hampir pasti UTF-16, kecuali jika Anda memiliki kode untuk mengubahnya menjadi UTF-8. JavaScript menggunakan UTF-16 secara internal. Jika Anda memiliki teks atau file CSV, mulai string dengan '\ ufeff', Byte Order Mark untuk UTF-16BE, dan editor teks akan dapat membaca karakter non-ASCII dengan benar.
larspars
14
Cukup tambahkan atribut unduhan = "txt.csv" untuk memiliki nama file dan ekstensi yang tepat dan untuk memberi tahu OS Anda apa yang harus dilakukan dengannya.
elshnkhll
725

Solusi sederhana untuk browser siap 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>

Pemakaian

download('test.txt', 'Hello world!');
Matěj Pokorný
sumber
10
Ya. Inilah yang telah diposting oleh @MatthewFlaschen di sini sekitar 3 tahun yang lalu .
Joseph Silber
48
Ya, tetapi dengan downloadatribut Anda dapat menentukan nama file ;-)
Matěj Pokorný
2
Seperti @earcam telah tunjukkan dalam komentar di atas .
Joseph Silber
4
Chrome hanya menambahkan txtekstensi jika Anda tidak memberikan ekstensi dalam nama file. Jika Anda melakukannya, download("data.json", data)itu akan berfungsi seperti yang diharapkan.
Carl Smith
6
Ini bekerja untuk saya di Chrome (73.0.3683.86), dan Firefox (66.0.2). Itu TIDAK bekerja di IE11 (11.379.17763.0) dan Edge (44.17763.1.0).
Sam
231

Semua solusi di atas tidak berfungsi di semua browser. Inilah yang akhirnya berfungsi pada IE 10+, Firefox dan Chrome (dan tanpa jQuery atau pustaka lainnya):

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

Perhatikan bahwa, tergantung pada situasi Anda, Anda mungkin juga ingin memanggil URL.revokeObjectURL setelah menghapus elem. Menurut dokumen untuk URL.createObjectURL :

Setiap kali Anda memanggil createObjectURL (), URL objek baru dibuat, bahkan jika Anda telah membuatnya untuk objek yang sama. Masing-masing harus dilepaskan dengan memanggil URL.revokeObjectURL () ketika Anda tidak lagi membutuhkannya. Browser akan merilis ini secara otomatis ketika dokumen diturunkan; namun, untuk kinerja dan penggunaan memori yang optimal, jika ada waktu aman ketika Anda dapat membongkar secara eksplisit, Anda harus melakukannya.

Ludovic Feltz
sumber
7
Terima kasih banyak. Saya sudah mencoba semua contoh yang tercantum di sini dan hanya yang ini berfungsi dengan browser apa pun. Ini harus menjadi jawaban yang diterima.
LEM
Di Chrome, saya sebenarnya tidak harus menambahkan elemen ke tubuh untuk membuatnya berfungsi.
JackMorrissey
1
Untuk aplikasi AngularJS 1.x, Anda dapat membangun array Url saat dibuat dan kemudian membersihkannya dalam fungsi $ onDestroy pada komponen. Ini bekerja dengan baik untuk saya.
Splaktar
1
Jawaban lain mengarah ke Failed: network errorChrome. Yang ini berfungsi dengan baik.
juniper-
4
Ini bekerja untuk saya di Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) dan Edge (44.17763.1.0).
Sam
185

Semua contoh di atas berfungsi dengan baik di chrome dan IE, tetapi gagal di Firefox. Harap pertimbangkan untuk menambahkan jangkar ke badan dan menghapusnya setelah klik.

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
sumber
4
Namun: ada bug terbuka di IE 10 (dan saya masih melihatnya di 11) yang melempar "Akses ditolak" di a.click()telepon karena menganggap URL gumpalan adalah lintas-asal.
Matt
@Mat data uri adalah asal silang di beberapa browser. Sejauh yang saya tahu, tidak hanya di msie, tetapi di chrome juga. Anda dapat mengujinya dengan mencoba menyuntikkan javascript dengan data uri. Itu tidak akan dapat mengakses bagian lain dari situs ...
inf3rno
7
"Semua contoh di atas berfungsi dengan baik di chrome dan IE, tetapi gagal di Firefox." Karena urutan jawaban dapat berubah dari waktu ke waktu, tidak jelas jawaban mana yang berada di atas Anda ketika Anda menulis ini. Bisakah Anda menunjukkan dengan tepat pendekatan mana yang tidak berfungsi di Firefox?
Kevin
120

Saya senang menggunakan FileSaver.js . Kompatibilitasnya cukup bagus (IE10 + dan yang lainnya), dan sangat mudah digunakan:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");
Daniel Buckmaster
sumber
Ini berfungsi baik di Chrome. Bagaimana cara saya mengizinkan pengguna menentukan lokasi file pada disk?
gregm
6
Wow, terima kasih atas perpustakaan yang mudah digunakan. Ini adalah jawaban terbaik, dan siapa yang peduli dengan orang yang menggunakan HTML <5 hari ini?
notbad.jpeg
@regreg, saya tidak yakin Anda bisa menggunakan plugin ini.
Daniel Buckmaster
@regreg: Maksud Anda lokasi pengunduhan? Itu tidak berhubungan dengan FileSaver.js, Anda perlu mengatur konfigurasi browser Anda sehingga meminta folder sebelum setiap download, atau gunakan agak baru atribut-download di <a>.
CodeManX
1
Ini adalah solusi HEBAT untuk browser IE 10+. IE belum mendukung tag unduhan HTML 5 dan solusi lain pada halaman ini (dan halaman SO lainnya yang membahas masalah yang sama) sama sekali tidak berfungsi untuk saya. FileSaver ftw!
TMc
22

Metode berikut berfungsi di IE11 +, Firefox 25+ dan 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>

Lihat ini di Action: http://jsfiddle.net/Kg7eA/

Firefox dan Chrome mendukung URI data untuk navigasi, yang memungkinkan kami membuat file dengan menavigasi ke data URI, sementara IE tidak mendukungnya untuk tujuan keamanan.

Di sisi lain, IE memiliki API untuk menyimpan gumpalan, yang dapat digunakan untuk membuat dan mengunduh file.

dinesh ygv
sumber
Saya baru saja menggunakan jquery untuk melampirkan acara (onclick dan onready) dan mengatur atribut, yang juga dapat Anda lakukan dengan vanilla JS. Bagian inti (window.navigator.msSaveOrOpenBlob) tidak perlu jquery.
dinesh ygv
Masih ada batasan ukuran untuk pendekatan data uri, bukan?
phil
msSaveOrOpenBlob ditampilkan sebagai usang di sini: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat
13

Solusi ini diekstraksi langsung dari repositori github tiddlywiki's (tiddlywiki.com). Saya telah menggunakan tiddlywiki di hampir semua browser dan berfungsi seperti pesona:

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: Unduh modul saver

Danielo515
sumber
Ini bekerja dengan sangat baik di Chrome, tetapi tidak di Firefox. Itu memang membuat file dan mengunduhnya, tetapi file itu kosong. Tidak ada isi. Ada ide mengapa? Belum diuji pada IE ...
Narxx
2
kecuali bahwa fungsinya tidak memiliki nama, ini adalah favorit saya
Yoraco Gonzales
11

Solusi yang bekerja pada IE10: (Saya membutuhkan file csv, tapi cukup untuk mengubah jenis dan nama file menjadi 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
sumber
1
Jawaban Ludovic termasuk yang besar ini, ditambah dukungan untuk browser lain.
Dan Dascalescu
10

Jika Anda hanya ingin mengonversi string yang tersedia untuk diunduh, Anda dapat mencoba ini menggunakan jQuery.

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));
Rick
sumber
1
Scape data dengan encodeURI mungkin diperlukan seperti yang saya sarankan di sini sebelum dapat berkomentar: stackoverflow.com/a/32441536/4928558
atfornes
10

Paket js-file-download dari github.com/kennethjiang/js-file-download menangani kasus tepi untuk dukungan browser:

Lihat sumber untuk melihat bagaimana menggunakan teknik yang disebutkan di halaman ini.

Instalasi

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

Pemakaian

import fileDownload from 'js-file-download'

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

fileDownload(data, 'filename.csv', 'text/csv')
Beau Smith
sumber
1
Terima kasih - baru saja diuji - bekerja dengan Firefox, Chrome dan Edge pada Windows
Brian Burns
8

Seperti disebutkan sebelumnya, filesaver adalah paket yang bagus untuk bekerja dengan file di sisi klien. Tapi, itu tidak baik dengan file besar. StreamSaver.js adalah solusi alternatif (yang ditunjukkan dalam FileServer.js) yang dapat menangani file besar:

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
sumber
1
Text Encoder sangat eksperimental sekarang, saya sarankan menghindari (atau polyfilling) itu.
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
sumber
1
Apa perbedaan antara pendekatan ini dan menciptakan gumpalan?
Dan Dascalescu
5

Berdasarkan jawaban @Rick yang sangat membantu.

Anda harus mencetak string datajika ingin membagikannya dengan cara ini:

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

`Maaf saya tidak bisa mengomentari jawaban @Rick karena reputasi saya yang rendah di StackOverflow.

Sebuah mengedit saran dibagi dan ditolak.

atfornes
sumber
1
Saya tidak dapat menerima saran itu. Aneh ... Saya memperbarui kode.
Rick
4

Kita dapat menggunakan api URL , khususnya URL.createObjectURL () , dan Blob api untuk menyandikan dan mengunduh apa saja.

Jika unduhan Anda kecil, ini berfungsi dengan baik:

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


Jika unduhan Anda besar, alih-alih menggunakan DOM, cara yang lebih baik adalah membuat elemen tautan dengan parameter unduhan, dan memicu klik.

Perhatikan bahwa elemen tautan tidak ditambahkan ke dokumen tetapi klik itu berfungsi! Ini dimungkinkan untuk membuat unduhan ratusan Mo dengan cara ini.

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! Unduh objek siklik apa pun , hindari kesalahan:

TypeError: nilai objek siklik (Firefox) TypeError: Konversi

struktur melingkar ke JSON (Chrome dan Opera) TypeError: Circular

referensi dalam argumen nilai tidak didukung (Tepi)

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

Pada contoh ini, mengunduh documentobjek sebagai 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
sumber
2

Fungsi di bawah ini berfungsi.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }
giapnh
sumber
dapatkah Anda membuka file di tab baru menjaga fileName ditetapkan, tetapi tidak mengunduh, hanya membuka di tab?
Carl Kroeger Ihl
1

Metode berikut berfungsi di IE10 +, Edge, Opera, FF dan 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);
    }
}

Jadi, panggil saja fungsinya:

saveDownloadedData('test.txt', 'Lorem ipsum');
Denys Rusov
sumber
0

Bagi saya ini berfungsi dengan baik, dengan nama file dan ekstensi yang sama diunduh

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

'title' adalah nama file dengan ekstensi yaitu, sample.pdf, waterfall.jpg, dll ..

'file64' adalah konten base64 seperti ini yaitu, Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO

abdul
sumber
0

Saya akan menggunakan <a></a>tag kemudian mengatur href='path'. Setelah itu, tempatkan gambar di antara <a>elemen - elemen tersebut sehingga saya dapat memiliki visual untuk melihatnya. Jika ingin, Anda dapat membuat fungsi yang akan mengubahhref sehingga tidak hanya menjadi tautan yang sama tetapi menjadi dinamis.

Berikan <a>tag idjuga jika Anda ingin mengaksesnya dengan javascript.

Dimulai dengan Versi 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>

Sekarang dengan 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
sumber
-22

Jika file berisi data teks, teknik yang saya gunakan adalah memasukkan teks ke dalam elemen textarea dan meminta pengguna untuk memilihnya (klik pada textarea lalu ctrl-A) kemudian salin diikuti oleh tempel ke editor teks.

HBP
sumber
30
Saya telah mempertimbangkan hal itu, tetapi dari sudut pandang ramah pengguna, ini adalah bencana. Selain itu, file harus disimpan dengan ekstensi CSV. Coba katakan itu kepada pengguna Anda.
Joseph Silber