Як створити файл у пам'яті, який користувач може завантажити, але не через сервер?


823

Чи є спосіб створити текстовий файл на стороні клієнта і запропонувати користувачеві завантажити його без будь-якої взаємодії з сервером? Я знаю, що я не можу писати безпосередньо на їх машину (безпека та всі), але чи можу я створити та запропонувати їх зберегти?


Відповіді:


433

Ви можете використовувати URI даних. Підтримка браузера змінюється; див. Вікіпедію . Приклад:

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

Октет-потік повинен змусити запит на завантаження. В іншому випадку він, ймовірно, відкриється в браузері.

Для CSV ви можете використовувати:

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

Спробуйте демонстрацію jsFiddle .


19
Це не крос-браузерне рішення, але, безумовно, щось, на що варто поглянути. Наприклад, IE обмежує підтримку uri даних. IE 8 обмежує розмір до 32 КБ, а IE 7 і нижче зовсім не підтримує.
Дарин Димитров

8
у версії Chrome 19.0.1084.46 цей метод генерує таке попередження: "Ресурс інтерпретується як Документ, але передається з MIME типу text / csv:" дані: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " Завантаження не спрацьовує
Кріс

2
Зараз він працює в Chrome (протестовано проти v20 та v21), але не IE9 (це може бути просто jsFiddle, але я якось сумніваюся в цьому).
вушка

5
Правильна графіка майже напевно UTF-16, якщо у вас немає коду, який перетворює її на UTF-8. JavaScript використовує UTF-16 внутрішньо. Якщо у вас є текстовий або CSV-файл, почніть рядок з «\ ufeff», позначки порядку в байтах для UTF-16BE, а редактори тексту зможуть правильно читати символи, що не належать до ASCII.
larspars

14
Просто додайте атрибут download = "txt.csv", щоб мати належне ім'я файлу та розширення та сказати вашій операційній системі, що з ним робити.
elshnkhll

724

Просте рішення для готових браузерів 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>

Використання

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


48
Так, але за допомогою downloadатрибуту ви можете вказати ім'я файлу ;-)
Matěj Pokorný

2
Як @earcam вже зазначав у коментарях вище .
Джозеф Сільбер

4
Chrome додає txtрозширення лише у тому випадку, якщо ви не вказали розширення у назві файлу. Якщо ви download("data.json", data)це зробите, то буде працювати, як очікувалося.
Карл Сміт

6
Це працювало для мене в Chrome (73.0.3683.86) та Firefox (66.0.2). Він НЕ працює в IE11 (11.379.17763.0) та Edge (44.17763.1.0).
Сем

231

Усі вищезазначені рішення працювали не у всіх браузерах. Ось що, нарешті, працює в IE 10+, Firefox та Chrome (і без jQuery чи будь-якої іншої бібліотеки):

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

Зауважте, що, залежно від вашої ситуації, ви можете також зателефонувати за URL.revokeObjectURL після видалення elem. Відповідно до документів для URL.createObjectURL :

Кожен раз, коли ви викликаєте createObjectURL (), створюється нова URL-адреса об'єкта, навіть якщо ви вже створили один і той же об’єкт. Кожен з них повинен бути звільнений, зателефонувавши за URL.revokeObjectURL (), коли вони вам більше не потрібні. Браузери автоматично випускають їх, коли документ завантажується; однак для оптимальної продуктивності та використання пам'яті, якщо є безпечні часи, коли ви можете явно їх розвантажувати, слід це зробити.


7
Завдяки мільйонів. Я спробував усі наведені тут приклади, і лише цей працює з будь-яким браузером. Це має бути прийнятою відповіддю.
ЛЕМ

У Chrome мені насправді не довелося додавати елемент до корпусу, щоб змусити це працювати.
JackMorrissey

1
Для програм AngularJS 1.x ви можете створити масив URL-адрес під час їх створення, а потім очистити їх у функції $ onDestroy компонента. Це чудово працює для мене.
Splaktar

1
Інші відповіді призвели до Failed: network errorChrome. Цей добре працює.
ялівець-

4
Це працювало для мене в Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) та Edge (44.17763.1.0).
Сем

185

Усі вищенаведені приклади добре працюють у хромі та IE, але не виходять із Firefox. Не забудьте приєднати якір до тіла та зняти його після клацання.

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
Однак: в IE 10 є відкрита помилка (і я досі її бачила в 11), яка виводить на a.click()рядок "Доступ заборонено", оскільки він вважає, що URL-адреса кробу має перехресне походження.
Метт

Дані @Matt урі в багатьох браузерах є поперечним походженням. наскільки я знаю, не тільки в msie, але і в хромі. ви можете протестувати, намагаючись ввести javascript з урі даних. Він не зможе отримати доступ до інших частин сайту ...
inf3rno

7
"Усі вищенаведені приклади спрацьовують як у хромі, так і в IE, але не в Firefox." Оскільки порядок відповідей може змінюватися з часом, незрозуміло, які відповіді були вище вашого, коли ви це писали. Чи можете ви точно вказати, які підходи не працюють у Firefox?
Кевін

120

Я із задоволенням використовую FileSaver.js . Його сумісність досить хороша (IE10 + та все інше), і користуватися нею дуже просто:

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

Це чудово працює в Chrome. Як дозволити користувачеві вказати розташування файлу на диску?
gregm

6
Нічого, дякую за просту у користуванні бібліотеку. Це легко найкраща відповідь, і хто дбає про людей, які використовують HTML <5 сьогодні?
notbad.jpeg

@gregm Я не впевнений, що ти можеш користуватися цим плагіном.
Даніель Бакмастер

@gregm: Ви маєте на увазі місце завантаження? Це не пов'язано з FileSaver.js, вам необхідно встановити конфігурацію браузера так , що він запитує папку перед кожним завантаженням, або використовувати замість нової завантаження атрибут на <a>.
CodeManX

1
Це ВЕЛИКЕ рішення для сімейства браузерів IE 10+. IE ще не підтримує тег HTML 5 для завантаження, а інші рішення на цій сторінці (та інших сторінках ТА, що обговорюють ту саму проблему) просто не працювали для мене. FileSaver ftw!
TMc

22

У IE11 +, Firefox 25+ та 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>

Дивіться це в дії: http://jsfiddle.net/Kg7eA/

Firefox та Chrome підтримують URI даних для навігації, що дозволяє нам створювати файли, переходячи до URI даних, в той час як IE не підтримує його в цілях безпеки.

З іншого боку, IE має API для збереження краплі, який можна використовувати для створення та завантаження файлів.


Я просто використав jquery, щоб приєднати події (onclick та onready) та встановити атрибути, які ви також можете зробити з ванільним JS. Основна частина (window.navigator.msSaveOrOpenBlob) не потребує jquery.
dinesh ygv

Все ще існує обмеження розміру для підходу даних урі, чи не так?
філ

msSaveOrOpenBlob показаний як застарілий тут: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat

13

Це рішення витягується безпосередньо з сховища github tiddlywiki (tiddlywiki.com). Я використовував tiddlywiki майже в усіх браузерах, і це працює як принадність:

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: Завантажте модуль збереження


Це дуже добре працює на Chrome, але не на Firefox. Він робить файл і завантажує його, але файл порожній. Немає вмісту. Будь-які ідеї чому? Не перевірений на IE ...
Narxx

2
за винятком того, що функція не має імені, це моє улюблене
Yoraco Gonzales

11

Рішення, яке працює над IE10: (мені потрібен був файл csv, але досить змінити тип і ім'я файлу на 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
Відповідь Людовика включає цю велику, а також підтримку інших браузерів.
Дан Даскалеску

10

Якщо ви просто хочете перетворити рядок, який буде доступний для завантаження, ви можете спробувати це за допомогою jQuery.

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

1
Scape дані з encodeURI можуть бути необхідні , як я запропонував тут , перш ніж бути в змозі коментарів: stackoverflow.com/a/32441536/4928558
atfornes

10

Пакет js-file-download з github.com/kennethjiang/js-file-download обробляє крайові випадки для підтримки браузера:

Переглянути джерело щоб побачити, як він використовує методи, згадані на цій сторінці.

Установка

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

Використання

import fileDownload from 'js-file-download'

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

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

1
Дякую - щойно перевірено - працює з Firefox, Chrome та Edge на Windows
Брайан Бернс

8

Як згадувалося раніше, зберігач файлів - це чудовий пакет для роботи з файлами на стороні клієнта. Але, з великими файлами це не дуже добре. StreamSaver.js - це альтернативне рішення (яке вказано на FileServer.js), яке може обробляти великі файли:

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 зараз дуже експериментальний, я б пропонував уникати (або поліфікувати) його.
Брендон.Бланшард

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
Які відмінності між цим підходом та створенням Blob?
Дан Даскалеску


5

На основі відповіді @Rick, яка була дуже корисною.

dataЯкщо ви хочете поділитися ним таким чином, вам потрібно відкинути рядок :

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

`Вибачте, що не можу коментувати відповідь @ Rick через мою нинішню низьку репутацію в StackOverflow.

Редагувати пропозицію поділяв і відкинуто.


1
Я не зміг прийняти пропозицію. Дивно ... Я оновив код.
Рік

4

Ми можемо використовувати URL-адресу api, зокрема URL.createObjectURL () та Blob api для кодування та завантаження майже нічого.

Якщо завантаження невелике, це працює добре:

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


Якщо завантаження величезне, замість використання DOM, кращий спосіб - створити елемент посилання з параметрами завантаження та здійснити клацання.

Зверніть увагу, що елемент посилання не додається до документа, але натискання все одно працює! Це можливо створити завантаження багатьох сотень 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>


Бонус! Завантажуйте будь-які циклічні об’єкти , уникайте помилок:

TypeError: значення циклічного об'єкта (Firefox) TypeError: Перетворення

кругова структура для JSON (Chrome і Opera) TypeError: Circular

посилання в аргументі значення не підтримується (Edge)

Використання https://github.com/douglascrockford/JSON-js/blob/master/cycle.js

У цьому прикладі завантаження documentоб'єкта як 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

Ви навіть можете зробити це краще, ніж просто URI - використовуючи Chrome, ви також можете запропонувати ім’я, яке буде містити файл, як пояснено в цьому дописі про блог щодо іменування завантаження при використанні URI .



2

Ця функція нижче працювала.

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

чи можете ви відкрити файл на новій вкладці, зберігаючи присвоєне ім’я файлу, але не завантажуючи його, просто відкривши на вкладці?
Карл Крогер

1

У IE10 +, Edge, Opera, FF та 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);
    }
}

Отже, просто зателефонуйте до функції:

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

0

Для мене це працювало чудово, з тим самим ім'ям файлу та розширенням завантажувались

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

'title' - це ім'я файлу з розширенням, тобто sample.pdf,waterfall.jpg і т.д ..

"file64" - це вміст base64, подібний до цього, тобто Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO


0

Я б використав <a></a>тег, а потім встановив href='path'. Після цього помістіть зображення між <a>елементами, щоб я мав змогу побачити його наочне зображення. Якщо ви хочете, ви можете створити функцію, яка змінитьhref так, що вона не буде просто тим самим посиланням, а буде динамічною.

Дайте <a>тег idтакож, якщо ви хочете отримати доступ до нього за допомогою JavaScript.

Починаючи з версії 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>

Тепер із 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

Якщо файл містить текстові дані, я використовую техніку, щоб вставити текст у текстовий елемент та дозволити користувачеві вибрати його (натисніть у текстовій області, потім ctrl-A), а потім скопіювати, а потім вставити в текстовий редактор.


30
Я вважав це, але з точки зору зручності для користувачів це катастрофічно. Також файл потрібно зберегти з розширенням CSV. Спробуйте повідомити це своїм користувачам.
Джозеф Сільбер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.