Как создать файл в памяти для загрузки пользователем, но не через сервер?

827

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

Джозеф Силбер
источник
1
См. Также: JavaScript: создание и сохранение файла
Генри Вуди,

Ответы:

432

Вы можете использовать данные 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:« data: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ». " Загрузка не запущена
Крис
2
Теперь он работает в Chrome (протестировано с v20 и v21), но не с IE9 (это может быть просто jsFiddle, но почему-то я в этом сомневаюсь).
earcam
5
Правильный набор символов - это почти наверняка UTF-16, если только у вас нет кода для его преобразования в UTF-8. JavaScript использует UTF-16 для внутреннего использования. Если у вас есть текстовый или CSV-файл, начните строку с '\ ufeff', метки порядка байтов для UTF-16BE, и текстовые редакторы смогут правильно читать символы не ASCII.
Ларспарс
14
Просто добавьте атрибут download = "txt.csv", чтобы иметь правильное имя файла и расширение и сообщить своей ОС, что с ним делать.
elshnkhll
726

Простое решение для 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!');
Матей Покорны
источник
10
Ага. Это именно то, что @MatthewFlaschen разместил здесь около 3 лет назад .
Джозеф Силбер
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
Бесконечно благодарен. Я перепробовал все примеры, перечисленные здесь, и только этот работает с любым браузером. Это должен быть принятый ответ.
LEM
В Chrome мне не нужно было добавлять элемент в тело, чтобы заставить его работать.
ДжекМоррисси
1
Для приложений AngularJS 1.x вы можете создать массив URL-адресов по мере их создания, а затем очистить их в функции $ onDestroy компонента. Это прекрасно работает для меня.
Сплактар
1
Другие ответы привели Failed: network errorв Chrome. Этот работает хорошо.
juniper-
4
Это сработало для меня в Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) и Edge (44.17763.1.0).
Сэм
185

Все вышеприведенные примеры прекрасно работают в Chrome и 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);
Naren
источник
4
Тем не менее: в IE 10 есть открытая ошибка (и я все еще видел ее в 11), которая выдает «Доступ запрещен» в a.click()строке, потому что он считает URL-адрес большого двоичного объекта перекрестным.
Мэтт
@Matt data uri является перекрестным источником в некоторых браузерах. насколько я знаю, не только в msie, но и в chrome. Вы можете проверить это, пытаясь внедрить JavaScript с данными URI. Он не сможет получить доступ к другим частям сайта ...
inf3rno
7
«Все вышеприведенные примеры прекрасно работают в Chrome и IE, но не работают в Firefox». Поскольку порядок ответов со временем может меняться, неясно, какие ответы были выше ваших, когда вы это писали. Можете ли вы указать, какие именно подходы не работают в Firefox?
Кевин
120

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

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");
Даниэль Бакмастер
источник
Это прекрасно работает на Chrome. Как мне разрешить пользователю указывать местоположение файла на диске?
Грегм
6
Вау, спасибо за простую в использовании библиотеку. Это, пожалуй, лучший ответ, и кого сейчас волнует, что люди используют HTML <5?
notbad.jpeg
@gregm Я не уверен, что вы можете с этим плагином.
Даниэль Бакмастер
@gregm: Вы имеете в виду место загрузки? Это не связанно с FileSaver.js, вам необходимо установить конфигурацию браузера так , что он запрашивает папку перед каждой загрузкой, или использовать вместо новой загрузки атрибут на <a>.
CodeManX
1
Это отличное решение для браузеров IE 10+. IE пока не поддерживает загрузочный тег HTML 5, и другие решения на этой странице (и другие SO-страницы, обсуждающие ту же проблему) просто не работают для меня. 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) и установки атрибутов, что вы также можете сделать с vanilla JS. Основная часть (window.navigator.msSaveOrOpenBlob) не нуждается в jquery.
Динеш Игв
Для подхода data uri все еще существует ограничение размера, не так ли?
Фил
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: Скачать модуль заставки

Danielo515
источник
Это очень хорошо работает на 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")
Dzarek
источник
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

Как упоминалось ранее, filesaver - отличный пакет для работы с файлами на стороне клиента. Но это не очень хорошо с большими файлами. 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));

`Извините, я не могу комментировать ответ @ Рика из-за моей низкой репутации в StackOverflow.

Предложение по редактированию было передано и отклонено.

atfornes
источник
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 лучше создать элемент ссылки с параметрами загрузки и вызвать щелчок.

Обратите внимание, что элемент ссылки не добавляется к документу, но щелчок работает в любом случае! Это позволяет создать загрузку многих сотен Мо таким образом.

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

NVRM
источник
2

Эта функция ниже работает.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }
giapnh
источник
Вы можете открыть файл в новой вкладке, сохраняя присвоенное имя файла, но не загружая, а просто открывая вкладку?
Карл Крегер
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

Abdul
источник
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

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

НВР
источник
30
Я считал это, но с точки зрения удобства для пользователя, это катастрофически. Кроме того, файл должен быть сохранен с расширением CSV. Попробуйте рассказать об этом своим пользователям.
Джозеф Силбер