Làm cách nào để tạo một tệp trong bộ nhớ để người dùng tải xuống, nhưng không thông qua máy chủ?


824

Có cách nào tôi có thể tạo một tệp văn bản ở phía máy khách và nhắc người dùng tải xuống mà không cần bất kỳ tương tác nào với máy chủ không? Tôi biết tôi không thể viết trực tiếp vào máy của họ (bảo mật và tất cả), nhưng tôi có thể tạo và nhắc họ lưu nó không?


Câu trả lời:


432

Bạn có thể sử dụng URI dữ liệu. Hỗ trợ trình duyệt khác nhau; xem Wikipedia . Thí dụ:

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

Luồng octet là để buộc một dấu nhắc tải xuống. Nếu không, nó có thể sẽ mở trong trình duyệt.

Đối với CSV, bạn có thể sử dụng:

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

Hãy thử bản demo jsFiddle .


19
Đây không phải là một giải pháp trình duyệt chéo nhưng chắc chắn là một cái gì đó đáng xem xét. Ví dụ: IE giới hạn hỗ trợ dữ liệu uri. IE 8 giới hạn kích thước ở 32KB và IE 7 và thấp hơn không hỗ trợ gì cả.
Darin Dimitrov

8
trong Chrome Phiên bản 19.0.1084.46, phương pháp này tạo ra cảnh báo sau: "Tài nguyên được hiểu là Tài liệu nhưng được chuyển bằng văn bản loại MIME / csv:" data: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " Tải xuống không được kích hoạt
Chris

2
Hiện tại nó hoạt động trong Chrome (đã được thử nghiệm so với v20 và v21) nhưng không phải IE9 (đó có thể chỉ là jsFiddle, nhưng bằng cách nào đó tôi nghi ngờ điều đó).
Earcam

5
Bộ ký tự chính xác gần như chắc chắn là UTF-16, trừ khi bạn có mã chuyển đổi nó thành UTF-8. JavaScript sử dụng UTF-16 trong nội bộ. Nếu bạn có tệp văn bản hoặc tệp CSV, hãy bắt đầu chuỗi bằng '\ ufeff', Dấu thứ tự Byte cho UTF-16BE và trình soạn thảo văn bản sẽ có thể đọc chính xác các ký tự không phải ASCII.
larspars

14
Chỉ cần thêm thuộc tính download = "txt.csv" để có tên tệp và phần mở rộng thích hợp và cho hệ điều hành của bạn biết phải làm gì với nó.
elshnkhll

724

Giải pháp đơn giản cho các trình duyệt sẵn sàng 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>

Sử dụng

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

10
Vâng. Đây chính xác là những gì @MatthewFlaschen đã đăng ở đây khoảng 3 năm trước .
Joseph Silber

48
Có, nhưng với downloadthuộc tính, bạn có thể chỉ định tên tệp ;-)
Matěj Pokorný

2
Như @earcam đã chỉ ra trong các ý kiến ​​trên .
Joseph Silber

4
Chrome chỉ nối thêm txttiện ích mở rộng nếu bạn không cung cấp tiện ích mở rộng trong tên tệp. Nếu bạn làm download("data.json", data)nó sẽ làm việc như mong đợi.
Carl Smith

6
Điều này làm việc với tôi trong Chrome (73.0.3683.86) và Firefox (66.0.2). Nó KHÔNG hoạt động trong IE11 (11.379.17763.0) và Edge (44.17763.1.0).
Sam

231

Tất cả các giải pháp trên không hoạt động trong tất cả các trình duyệt. Đây là những gì cuối cùng hoạt động trên IE 10+, Firefox và Chrome (và không có jQuery hoặc bất kỳ thư viện nào khác):

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

Lưu ý rằng, tùy thuộc vào tình huống của bạn, bạn cũng có thể muốn gọi URL.revokeObjectURL sau khi xóa elem. Theo các tài liệu cho URL.createObjectURL :

Mỗi lần bạn gọi createdObjectURL (), một URL đối tượng mới sẽ được tạo, ngay cả khi bạn đã tạo một URL cho cùng một đối tượng. Mỗi trong số này phải được phát hành bằng cách gọi URL.revokeObjectURL () khi bạn không còn cần chúng nữa. Các trình duyệt sẽ tự động giải phóng chúng khi tài liệu được tải; tuy nhiên, để sử dụng hiệu suất và bộ nhớ tối ưu, nếu có thời gian an toàn khi bạn có thể dỡ chúng một cách rõ ràng, bạn nên làm như vậy.


7
Cảm ơn rất nhiều. Tôi đã thử tất cả các ví dụ được liệt kê ở đây và chỉ có ví dụ này hoạt động với bất kỳ trình duyệt nào. Đây phải là câu trả lời được chấp nhận.
LEM

Trong Chrome, tôi thực sự không phải gắn phần tử vào phần thân để làm cho phần này hoạt động.
JackMorrissey

1
Đối với các ứng dụng AngularJS 1.x, bạn có thể tạo một mảng Url khi chúng được tạo và sau đó dọn sạch chúng trong hàm $ onDestroy của thành phần. Điều này đang làm việc rất tốt cho tôi.
Splaktar

1
Các câu trả lời khác dẫn đến Failed: network errortrong Chrome. Cái này hoạt động tốt.
cây bách xù-

4
Điều này làm việc với tôi trong Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) và Edge (44.17763.1.0).
Sam

185

Tất cả các ví dụ trên chỉ hoạt động tốt trong chrome và IE, nhưng thất bại trong Firefox. Vui lòng xem xét việc gắn một mỏ neo vào cơ thể và loại bỏ nó sau khi nhấp chuột.

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
Tuy nhiên: có một lỗi mở trong IE 10 (và tôi vẫn thấy nó trong 11) ném "Truy cập bị từ chối" trên a.click()dòng vì cho rằng URL blob có nguồn gốc chéo.
Matt

@Matt dữ liệu uri có nguồn gốc chéo trong một số trình duyệt. theo như tôi biết, không chỉ trong msie, mà cả chrome. bạn có thể kiểm tra nó bằng cách thử javascript với dữ liệu uri. Nó sẽ không thể truy cập vào các phần khác của trang web ...
inf3rno

7
"Tất cả các ví dụ trên chỉ hoạt động tốt trong chrome và IE, nhưng thất bại trong Firefox.". Vì thứ tự câu trả lời có thể thay đổi theo thời gian, nên không rõ câu trả lời nào nằm trên câu trả lời của bạn khi bạn viết bài này. Bạn có thể chỉ ra chính xác phương pháp nào không hoạt động trong Firefox không?
Kevin

120

Tôi rất vui khi sử dụng FileSaver.js . Khả năng tương thích của nó khá tốt (IE10 + và mọi thứ khác), và nó rất đơn giản để sử dụng:

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

Điều này hoạt động rất tốt trên Chrome. Làm cách nào để cho phép người dùng chỉ định vị trí của tệp trên đĩa?
gregm

6
Wow, cảm ơn vì thư viện dễ sử dụng. Đây dễ dàng là câu trả lời tốt nhất, và ai quan tâm đến những người sử dụng HTML <5 ngày nay có cách nào không?
notbad.jpeg

@gregm Tôi không chắc bạn có thể với plugin này.
Daniel Buckmaster

@gregm: Ý bạn là vị trí tải xuống? Đó là không liên quan đến FileSaver.js, bạn cần phải thiết lập cấu hình trình duyệt của bạn để nó yêu cầu một thư mục trước mỗi lần download, hoặc sử dụng khá mới mẻ thuộc tính tải về <a>.
CodeManX

1
Đây là một giải pháp TUYỆT VỜI cho họ trình duyệt IE 10+. IE chưa hỗ trợ thẻ HTML 5 tải xuống và các giải pháp khác trên trang này (và các trang SO khác thảo luận về cùng một vấn đề) chỉ đơn giản là không hoạt động đối với tôi. Trình bảo vệ tệp ftw!
TMc

22

Phương pháp sau hoạt động trong IE11 +, Firefox 25+ và 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>

Xem điều này trong hành động: http://jsfiddle.net/Kg7eA/

Firefox và Chrome hỗ trợ URI dữ liệu để điều hướng, cho phép chúng tôi tạo tệp bằng cách điều hướng đến URI dữ liệu, trong khi IE không hỗ trợ cho mục đích bảo mật.

Mặt khác, IE có API để lưu một blob, có thể được sử dụng để tạo và tải xuống các tệp.


Tôi chỉ sử dụng jquery để đính kèm các sự kiện (onclick và on sẵn) và thiết lập các thuộc tính, điều mà bạn cũng có thể làm với vanilla JS. Phần cốt lõi (window.navigator.msSaveOrOpenBlob) không cần jquery.
dinesh ygv

Vẫn còn giới hạn về kích thước cho cách tiếp cận dữ liệu uri, phải không?
phil

msSaveOrOpenBlob được hiển thị là lỗi thời ở đây: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat

13

Giải pháp này được trích xuất trực tiếp từ kho lưu trữ github của tiddlywiki (tiddlywiki.com). Tôi đã sử dụng tiddlywiki trong hầu hết các trình duyệt và nó hoạt động như một cơ duyên:

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: Tải xuống mô-đun tiết kiệm


Nó hoạt động rất độc đáo trên Chrome, nhưng không phải trên Firefox. Nó tạo một tập tin và tải nó xuống, nhưng tập tin trống rỗng. Không có nội dung. Bất cứ ý tưởng tại sao? Chưa được thử nghiệm trên IE ...
Narxx

2
ngoại trừ chức năng không có tên, đây là chức năng yêu thích của tôi
Yoraco Gonzales

11

Giải pháp hoạt động trên IE10: (Tôi cần một tệp csv, nhưng nó đủ để thay đổi loại và tên tệp thành 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
Câu trả lời của Ludovic bao gồm điều này lớn, cộng với sự hỗ trợ cho các trình duyệt khác.
Dan Dascalescu

10

Nếu bạn chỉ muốn chuyển đổi một chuỗi có sẵn để tải xuống, bạn có thể thử điều này bằng jQuery.

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

1
Dữ liệu scape với encodeURI có thể cần thiết như tôi đã đề xuất ở đây trước khi có thể nhận xét: stackoverflow.com/a/32441536/4928558
atfornes

10

Gói js-file-download từ github.com/kennethjiang/js-file-doad xử lý các trường hợp cạnh để hỗ trợ trình duyệt:

Xem nguồn để xem cách nó sử dụng các kỹ thuật được đề cập trên trang này.

Cài đặt

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

Sử dụng

import fileDownload from 'js-file-download'

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

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

1
Cảm ơn - vừa được thử nghiệm - hoạt động với Firefox, Chrome và Edge trên Windows
Brian Burns

8

Như đã đề cập trước đó, filesaver là một gói tuyệt vời để làm việc với các tệp ở phía máy khách. Nhưng, nó không làm tốt với các tệp lớn. StreamSaver.js là một giải pháp thay thế (được chỉ ra trong FileServer.js) có thể xử lý các tệp lớn:

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
Trình mã hóa văn bản hiện đang mang tính thử nghiệm cao, tôi khuyên bạn nên tránh (hoặc polyfilling) nó.
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
Sự khác biệt giữa phương pháp này và tạo Blob là gì?
Dan Dascalescu


5

Dựa trên câu trả lời @Rick thực sự hữu ích.

Bạn phải cắt chuỗi datanếu bạn muốn chia sẻ nó theo cách này:

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

`Xin lỗi tôi không thể nhận xét về câu trả lời của @ Rick do danh tiếng thấp hiện tại của tôi trong StackOverflow.

Một đề nghị chỉnh sửa đã được chia sẻ và từ chối.


1
Tôi đã không thể chấp nhận đề nghị. Lạ thật ... Tôi đã cập nhật mã.
Rick

4

Chúng tôi có thể sử dụng URL api, cụ thể là URL.createObjectURL ()Blob api để mã hóa và tải xuống khá nhiều thứ.

Nếu tải xuống của bạn nhỏ, điều này hoạt động tốt:

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


Nếu tải xuống của bạn rất lớn, thay vì sử dụng DOM, cách tốt hơn là tạo thành phần liên kết với các tham số tải xuống và kích hoạt nhấp chuột.

Lưu ý rằng phần tử liên kết không được thêm vào tài liệu nhưng dù sao thì nhấp chuột vẫn hoạt động! Điều này có thể tạo ra một bản tải xuống của hàng trăm Mo theo cách này.

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>


Tặng kem! Tải xuống bất kỳ đối tượng tuần hoàn , tránh các lỗi:

TypeError: giá trị đối tượng tuần hoàn (Firefox) TypeError: Chuyển đổi

cấu trúc vòng tròn thành JSON (Chrome và Opera) TypeError: Thông tư

tham chiếu trong đối số giá trị không được hỗ trợ (Edge)

Sử dụng https://github.com/douglascrockford/JSON-js/blob/master/ Motorcycle.js

Trong ví dụ này, tải documentđối tượng dưới dạng 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

Chức năng dưới đây đã làm việc.

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

bạn có thể mở tệp trong một tab mới giữ tên tệp được gán, nhưng không tải xuống, chỉ mở trong một tab không?
Carl Kroeger Ihl

1

Phương pháp sau hoạt động trong IE10 +, Edge, Opera, FF và 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);
    }
}

Vì vậy, chỉ cần gọi hàm:

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

0

Đối với tôi điều này hoạt động hoàn hảo, với cùng tên tệp và phần mở rộng được tải xuống

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

'tiêu đề' là tên tập tin với phần mở rộng tức là, sample.pdf, waterfall.jpg, vv ..

'file64' là nội dung cơ sở 64 giống như thế này, vd Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO


0

Tôi sẽ sử dụng một <a></a>thẻ sau đó thiết lập href='path'. Sau đó, đặt một hình ảnh ở giữa các <a>yếu tố để tôi có thể có một hình ảnh để xem nó. Nếu bạn muốn, bạn có thể tạo một chức năng sẽ thay đổihref để nó không chỉ là cùng một liên kết mà là động.

Cung cấp <a>thẻ idcũng như nếu bạn muốn truy cập nó bằng javascript.

Bắt đầu với Phiên bản 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>

Bây giờ với 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

Nếu tệp chứa dữ liệu văn bản, một kỹ thuật tôi sử dụng là đặt văn bản vào thành phần textarea và yêu cầu người dùng chọn nó (nhấp vào textarea sau đó ctrl-A) sau đó sao chép bằng cách dán vào trình soạn thảo văn bản.


30
Tôi đã xem xét điều đó, nhưng từ quan điểm thân thiện với người dùng, điều này thật tai hại. Ngoài ra, tệp phải được lưu với phần mở rộng CSV. Hãy thử nói với người dùng của bạn.
Joseph Silber
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.