Использование HTML5 / JavaScript для генерации и сохранения файла

317

В последнее время я возился с WebGL и заставил работать читателя Collada. Проблема в том, что он довольно медленный (Collada - очень многословный формат), поэтому я собираюсь начать преобразование файлов в более простой формат (возможно, JSON). У меня уже есть код для синтаксического анализа файла в JavaScript, поэтому я могу также использовать его как мой экспортер! Проблема в сохранении.

Теперь я знаю, что могу проанализировать файл, отправить результат на сервер и сделать так, чтобы браузер запросил файл с сервера для загрузки. Но на самом деле сервер не имеет ничего общего с этим конкретным процессом, так зачем его привлекать? У меня уже есть содержимое нужного файла в памяти. Можно ли как-то представить пользователю загрузку с использованием чистого JavaScript? (Я сомневаюсь в этом, но с таким же успехом могу спросить ...)

И чтобы быть ясным: я не пытаюсь получить доступ к файловой системе без ведома пользователей! Пользователь предоставит файл (возможно, с помощью перетаскивания), скрипт преобразует файл в памяти, и пользователю будет предложено загрузить результат. Все это должно быть «безопасной» деятельностью, если речь идет о браузере.

[РЕДАКТИРОВАТЬ]: Я не упомянул об этом заранее, поэтому постеры, которые ответили «Flash», достаточно действительны, но часть того, что я делаю, - это попытка подчеркнуть, что можно сделать с чистым HTML5 ... так что Flash это прямо в моем случае. (Хотя это совершенно правильный ответ для любого, кто делает «настоящее» веб-приложение.) В таком случае мне кажется, что мне не повезло, если я не хочу привлекать сервер. Спасибо, в любом случае!

Тодзи
источник
8
Вы могли бы рассмотреть вопрос об изменении принятого ответа, похоже, теперь существует чисто HTML-путь
Pekka

Ответы:

256

Хорошо, создание данных: URI определенно помогает мне, благодаря Мэтью и Деннкстеру, указавшим эту возможность! Вот как я это делаю:

1) получить весь контент в строку с именем «content» (например, создав его там изначально или прочитав innerHTML тега уже созданной страницы).

2) Создайте URI данных:

uriContent = "data:application/octet-stream," + encodeURIComponent(content);

Будут ограничения по длине в зависимости от типа браузера и т. Д., Но, например, Firefox 3.6.12 работает по крайней мере до 256 КБ. Кодирование в Base64 вместо использования encodeURIComponent может сделать вещи более эффективными, но для меня это было нормально.

3) откройте новое окно и «перенаправьте» его на этот URI, запрашивая местоположение загрузки моей страницы, созданной JavaScript:

newWindow = window.open(uriContent, 'neuesDokument');

Вот и все.

Нок
источник
34
Если вы хотите избежать использования всплывающего окна, которое может быть заблокировано, вы можете установить location.hrefсодержимое. location.href = uriContent,
Алекс Турпин
12
Привет, я попробовал это, но он загружает файл как файл .part. Как я могу установить тип файла?
Седат Башар
6
@ SedatBaşar URI данных не поддерживают установку имени файла, вы должны полагаться на браузер, устанавливающий соответствующее расширение с использованием типа mime. Но если MIME-тип может отображаться браузером, он не загружает его, а отображает. Есть несколько других способов сделать это, но ни один из них не работает в IE <10.
Panzi
7
IE на самом деле не поддерживает URI данных, и Firefox, кажется, сохраняет файлы со случайным именем.
nylund
25
Я думаю, что мы делаем это сложнее, чем нужно. Откройте консоль JS на этой странице и поместите ее, location.href = "data:application/octet-stream," + encodeURIComponent(jQuery('#answer-4551467 .post-text').first().text());и она сохранит содержимое ответа @ Nøk в файл. У меня нет IE, чтобы проверить его, но он работает для webkit.
Бруно Броноски
278

Простое решение для HTML5 готовых браузеров ...

function download(filename, text) {
    var pom = document.createElement('a');
    pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    pom.setAttribute('download', filename);

    if (document.createEvent) {
        var event = document.createEvent('MouseEvents');
        event.initEvent('click', true, true);
        pom.dispatchEvent(event);
    }
    else {
        pom.click();
    }
}

использование

download('test.txt', 'Hello world!');
Матей Покорны
источник
2
Если вы знаете исходный URL, который хотите скачать, это лучшее решение!
Сидональдсон
31
Возможность установить имя файла делает его победителем.
Ом
9
Этот метод работал в Chrome до последнего обновления, которое я получил несколько дней назад (35.0.1916.114 м). Теперь это работает частично из-за того, что имя файла и расширение больше не работают (он всегда называет файл download.txt независимо от того, что передано.)
Sevin7
6
У меня есть Chrom 42.0.2311.90, и это работает для меня с ожидаемым именем файла.
Саурабх Кумар
4
Есть ли ограничение на количество данных, которые могут быть включены?
Каспар Ли
80

HTML5 определяет window.saveAs(blob, filename)метод. Это не поддерживается ни одним браузером сейчас. Но есть библиотека совместимости под названием FileSaver.js, которая добавляет эту функцию в большинство современных браузеров (включая Internet Explorer 10+). Internet Explorer 10 поддерживает navigator.msSaveBlob(blob, filename)метод ( MSDN ), который используется в FileSaver.js для поддержки Internet Explorer.

Я написал сообщение в блоге с более подробной информацией об этой проблеме.

Панзи
источник
1
Как насчет блокировки всплывающих окон? Поведение этой библиотеки похоже на решение @ Nøk. Обычный текст в Firefox открывается по-новому. Только Chrome пытается сохранить его, но он меняет расширение (мне нужен точечный файл без расширения).
Сиемборд
1
@ciembor (объектный URL +) вариант атрибута загрузки (который я использую с chrome) позволяет вам задать имя файла. У меня это работает в хроме.
Panzi
1
@ciembor ага, и всплывающее окно не блокируется, если его вызвал клик.
Panzi
6
FileSaver.js теперь поддерживает IE
Eli Grey
15
W3C говорит: Работа над этим документом была прекращена, и на него нельзя ссылаться или использовать в качестве основы для реализации.
WaiKit Kung
48

Сохранение больших файлов

Длинные URI данных могут вызвать проблемы с производительностью в браузерах. Другой вариант сохранения сгенерированных на стороне клиента файлов - поместить их содержимое в объект Blob (или файл) и создать ссылку для загрузки с помощью URL.createObjectURL(blob). Это возвращает URL, который можно использовать для получения содержимого большого двоичного объекта. BLOB-объект хранится в браузере до тех пор, пока не URL.revokeObjectURL()будет вызван URL-адрес или документ, который его создал, закрыт. Большинство веб-браузеров поддерживают URL-адреса объектов , Opera Mini - единственный, который их не поддерживает.

Принудительное скачивание

Если данные представляют собой текст или изображение, браузер может открыть файл, а не сохранять его на диск. Чтобы файл был загружен при нажатии на ссылку, вы можете использовать downloadатрибут. Однако не все веб-браузеры поддерживают атрибут загрузки . Другим вариантом является использование application/octet-streamв качестве mime-типа файла, но это приводит к тому, что файл представляется в виде двоичного двоичного объекта, что особенно неудобно для пользователя, если вы не указали имя файла или не можете его указать. См. Также « Принудительно открывать всплывающее окно« Сохранить как ... »при открытии текстовой ссылки, нажмите pdf в HTML »

Указание имени файла

Если BLOB-объект создается с помощью конструктора файлов, вы также можете задать имя файла, но только несколько веб-браузеров (включая Chrome и Firefox) поддерживают конструктор файлов . Имя файла также может быть указано в качестве аргумента downloadатрибута, но это зависит от тонны соображений безопасности . Internet Explorer 10 и 11 предоставляет собственный метод msSaveBlob для указания имени файла.

Пример кода

var file;
var data = [];
data.push("This is a test\n");
data.push("Of creating a file\n");
data.push("In a browser\n");
var properties = {type: 'text/plain'}; // Specify the file's mime-type.
try {
  // Specify the filename using the File constructor, but ...
  file = new File(data, "file.txt", properties);
} catch (e) {
  // ... fall back to the Blob constructor if that isn't supported.
  file = new Blob(data, properties);
}
var url = URL.createObjectURL(file);
document.getElementById('link').href = url;
<a id="link" target="_blank" download="file.txt">Download</a>

bcmpinc
источник
1
Могу ли я показать диалоговое окно (всплывающее окно), чтобы указать папку (каталог) для сохранения файла?
Кальвин
@Calvin: я обновил ответ, чтобы объяснить, как форсировать загрузку, и указать имя файла. Я полагаю, что для IE вы можете msSaveBlobоткрыть диалоговое окно «Сохранить как». Для других браузеров единственный вариант - вручную выбрать «Сохранить как» в контекстном меню ссылки для скачивания.
bcmpinc
1
@ Jek-fdrv: в Safari работают только URL-адреса BLOB-объектов. Атрибут загрузки и конструктор файлов не поддерживаются Safari, поэтому вы не можете принудительно загрузить файл, то есть, вероятно, большой двоичный объект будет открыт в самом браузере, и вы не сможете указать имя файла. Для данного примера вы все равно сможете загрузить файл с Safari, используя контекстное меню ссылки.
bcmpinc
Это очень полезный и информативный ответ. Еще одна вещь, которая может помочь таким людям, как я: после установки document.getElementById('link').href = url;ваш код может пойти дальше и запустить ссылку, используя метод элемента .click().
LarsH
36
function download(content, filename, contentType)
{
    if(!contentType) contentType = 'application/octet-stream';
        var a = document.createElement('a');
        var blob = new Blob([content], {'type':contentType});
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.click();
}
Ясир Энназк
источник
3
Какое влияние оказывает contentType? Для чего его используют?
Ури
3
Этот отлично работает даже в последнем Chrome, в отличие от ответа @ Matěj Pokorný. Спасибо.
Александр Амелькин
2
Это не работает для меня на FF36 или IE11. Если я заменю a.clickкод, используя, document.createEvent()как предложил Матей Покорны, он работает на FF, но не на IE. Я не пробовал Chrome.
Питер Халл
25

Взгляните на Doug Neiner's Downloadify, который является JavaScript-интерфейсом на основе Flash для этого.

Downloadify - это небольшая библиотека JavaScript + Flash, которая позволяет создавать и сохранять файлы на лету, в браузере, без взаимодействия с сервером.

Пекка
источник
6
Для большинства людей это, вероятно, ответ, который им понадобится. Поэтому, даже если он не соответствует моим конкретным требованиям (как описано выше), я отмечаю его как принятый ответ.
Тодзи
2
@ Тодзи ах, понятно. Может быть, переспросить и перефразировать под HTML 5баннером и пометить соответственно? Это, вероятно, привлечет тех пользователей, которые знают об этой конкретной области (я полагаю, все еще сравнительно небольшая толпа сейчас). Я уверен, что это можно сделать в HTML 5, но я понятия не имею, как.
Пекка
1
Был ли куплен / передан домен downloadify.info Downloadify, и если да, то есть ли новое местоположение? Нынешний сайт кажется совершенно не связанным с данным ответом.
Христос Хейворд
17
Это не отвечает на использование HTML5 ... - озаглавленного вопроса.
Ixx
5
@ Хорошо, если честно, это было добавлено после того, как ответ был опубликован. Тем не менее, вы правы. Ответ ниже должен быть принят
Пекка
17

Простое решение!

<a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD">Click here</a>

Работает во всех современных браузерах.

T.Todua
источник
Привет, вы знаете, как задать поведение атрибута "скачать", используя window.open или другую функцию javascript?
yucer
2
@yucer Нет атрибута загрузки (или какого-либо другого атрибута) с window.open(). Это не имеет значения. Вы можете использовать этот метод и принудительно использовать a .click(), но смотрите время, так как Firefox не понравится, если вы позвоните, .click()прежде чем позволить элементу присоединиться к телу.
Брэд
Но, к сожалению, все пробелы удаляются. В моем случае это очень плохо, так как я хочу скачать исходный код для файла SVG.
frankenapps
пробелы сохраняются, если вы используете encodeURIComponent (content)
Майк Редроб
не могу выбрать имя в Firefox, но Chrome работает
Bhikkhu Subhuti
10

Вы можете сгенерировать URI данных . Тем не менее, существуют ограничения для браузера.

Мэтью Флэшен
источник
1
Это интересно. Я посмотрю на это больше, когда у меня будет шанс. Спасибо!
Тодзи
@quantumpotato, фактическое создание URL-адреса немного сложно объяснить. Все мельчайшие частицы в RFC 2397 . Вы можете использовать такие инструменты , как это для тестирования. Затем для вашего реального приложения вы можете найти URI данных или библиотеку base 64 для вашего языка. Если вы не нашли его, не стесняйтесь задавать дополнительный вопрос. Некоторые ограничения для браузера приведены в статье в Википедии . Например, IE ограничивает длину и тип (например, не text / html).
Мэтью Флэшен
Генерация URL-адресов данных не так уж и сложна: "data:text/plain;charset=UTF-8,"+encodeURIComponent(text)но да, IE ограничивает размер URL-адресов данных до неиспользуемой суммы и не поддерживает их для таких вещей, как window.open(...)или фреймы (я думаю).
Panzi
@panzi, это тоже не так просто. Во-первых, это неправильный тип MIME для Collada или JSON.
Мэтью Флэшен
слишком неинформативный ответ. Пожалуйста, добавьте спецификации для браузеров, если вы их упомянули.
Т.Тодуа
10

Я использовал FileSaver ( https://github.com/eligrey/FileSaver.js ), и он отлично работает.
Например, я сделал эту функцию для экспорта журналов, отображаемых на странице.
Вы должны передать массив для создания экземпляра BLOB-объекта, так что я просто, возможно, не написал это правильно, но это работает для меня.
На всякий случай, будьте осторожны с заменой: это синтаксис, чтобы сделать этот глобальный, иначе он заменит только первый, который он встретит.

exportLogs : function(){
    var array = new Array();

    var str = $('#logs').html();
    array[0] = str.replace(/<br>/g, '\n\t');

    var blob = new Blob(array, {type: "text/plain;charset=utf-8"});
    saveAs(blob, "example.log");
}
Razakhel
источник
2
FileSaver хорош, вот IE Shim для функции до IE10 preIE10SaveAs: (имя файла, filecontent, mimetype) {var w = window.open (); var doc = w.document; doc.open (mimetype, 'replace'); doc.charset = "utf-8"; doc.write (filecontent); doc.close (); doc.execCommand («Сохранить как», null, имя файла); }
Aqm
Предупреждение о шиме @ aqm: он выполняет теги сценария.
GameFreak
Также может быть желательно поставить w.close();послеexecCommand
GameFreak
8

Я нашел два простых подхода, которые работают для меня. Во-первых, используя уже выбранный aэлемент и добавляя данные для загрузки. И, во-вторых, создание aэлемента с данными загрузки, выполнение a.click()и удаление его снова. Но второй подход работает только в том случае, если он вызывается действием пользователя. (Некоторые) Блокировка браузера click()из других контекстов, например, при загрузке или срабатывание после тайм-аута (setTimeout).

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript">
      function linkDownload(a, filename, content) {
        contentType =  'data:application/octet-stream,';
        uriContent = contentType + encodeURIComponent(content);
        a.setAttribute('href', uriContent);
        a.setAttribute('download', filename);
      }
      function download(filename, content) {
        var a = document.createElement('a');
        linkDownload(a, filename, content);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      }
    </script>
   </head>
  <body>
    <a href="#" onclick="linkDownload(this, 'test.txt', 'Hello World!');">download</a>
    <button onclick="download('test.txt', 'Hello World!');">download</button>
  </body>
</html>
Maikel
источник
2
Вы также можете вставить aв документ (возможно, с помощью "display:none"), щелкнуть по нему, а затем удалить его.
Teepeemm
1
будет ли это работать в браузерах, где атрибут загрузки не поддерживается, как даже современный ie и safari .. caniuse.com/#feat=download
Мухаммед Умер
1
Только что протестировал Safari 5.0 под вином. Первая версия работает, вторая нет. Я также протестировал IE 8 (wine), и он не работает.
Майкл
6

Вот ссылка на метод URI данных, предложенный Мэтью, он работал на сафари, но не очень хорошо, потому что я не мог установить тип файла, он сохраняется как «неизвестный», а затем мне нужно вернуться туда позже и изменить его по порядку. просмотреть файл ...

http://www.nihilogic.dk/labs/canvas2image/

Dennkster
источник
4

Вы можете использовать localStorage. Это HTML5-эквивалент файлов cookie. Похоже, что работает на Chrome и Firefox НО на Firefox, мне нужно было загрузить его на сервер. То есть тестирование непосредственно на моем домашнем компьютере не сработало.

Я работаю над примерами HTML5. Перейдите по адресу http://faculty.purchase.edu/jeanine.meyer/html5/html5explain.html и перейдите к лабиринту. Информация для восстановления лабиринта хранится с использованием localStorage.

Я пришел к этой статье в поисках HTML5 JavaScript для загрузки и работы с XML-файлами. Это так же, как старые HTML и JavaScript ????

Жанин
источник
4

пытаться

let a = document.createElement('a');
a.href = "data:application/octet-stream,"+encodeURIComponent('"My DATA"');
a.download = 'myFile.json';
a.click(); // we not add 'a' to DOM so no need to remove

Если вы хотите скачать двоичные данные, посмотрите здесь

Обновить

2020.06.14 Я обновляю Chrome до 83.0 и выше, так что остановка фрагмента работает (из-за ограничений безопасности песочницы ) - но версия JSFiddle работает - здесь

Камил Келчевски
источник
3

Как упоминалось ранее, File API вместе с FileWriter и FileSystem API-интерфейсами можно использовать для хранения файлов на клиентском компьютере из контекста вкладки / окна браузера.

Однако есть несколько вещей, относящихся к последним двум API, о которых вы должны знать:

  • Реализации API в настоящее время существуют только в браузерах на основе Chromium (Chrome & Opera)
  • 24 апреля 2014 года оба API-интерфейса были выведены из-под контроля стандартов W3C и являются собственностью компании.
  • Удаление (теперь проприетарных) API из реализации браузеров в будущем возможно
  • песочница хранения файлов, созданных с помощью API, используется (место на диске, вне которого файлы не могут оказывать никакого влияния)
  • Виртуальная файловая система (структура каталогов , которая не обязательно существует на диске в той же форме , что делает , когда доступ из браузера) используется представляют файлы , созданные с помощью API -

Вот простые примеры того, как API, прямо и косвенно, используются для этого:

BakedGoods *

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});

Использование сырых API File, FileWriter и FileSystem

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);

Хотя API FileSystem и FileWriter более не соответствуют стандартам, их использование в некоторых случаях может быть оправдано, на мой взгляд, потому что:

  • Возобновление интереса со стороны не реализующих поставщиков браузеров может вернуть их обратно
  • Проникновение на рынок внедряющих браузеров (на основе хрома)
  • Google (основной вкладчик в Chromium) не дал и API-интерфейсы с истекшим сроком эксплуатации

Однако, «некоторые случаи» охватывают ваши собственные, решать вам.

* BakedGoods поддерживается только этим парнем прямо здесь :)

Kevin
источник
2

Этот поток был неоценим, чтобы выяснить, как сгенерировать двоичный файл и предложить загрузить указанный файл, все в коде клиента без сервера.

Первым шагом для меня было создание двоичного двоичного объекта из данных, которые я сохранял. Существует множество примеров для выполнения этого для одного двоичного типа, в моем случае у меня есть двоичный формат с несколькими типами, который вы можете передать в виде массива для создания большого двоичного объекта.

saveAnimation: function() {

    var device = this.Device;
    var maxRow = ChromaAnimation.getMaxRow(device);
    var maxColumn = ChromaAnimation.getMaxColumn(device);
    var frames = this.Frames;
    var frameCount = frames.length;

    var writeArrays = [];


    var writeArray = new Uint32Array(1);
    var version = 1;
    writeArray[0] = version;
    writeArrays.push(writeArray.buffer);
    //console.log('version:', version);


    var writeArray = new Uint8Array(1);
    var deviceType = this.DeviceType;
    writeArray[0] = deviceType;
    writeArrays.push(writeArray.buffer);
    //console.log('deviceType:', deviceType);


    var writeArray = new Uint8Array(1);
    writeArray[0] = device;
    writeArrays.push(writeArray.buffer);
    //console.log('device:', device);


    var writeArray = new Uint32Array(1);
    writeArray[0] = frameCount;
    writeArrays.push(writeArray.buffer);
    //console.log('frameCount:', frameCount);

    for (var index = 0; index < frameCount; ++index) {

      var frame = frames[index];

      var writeArray = new Float32Array(1);
      var duration = frame.Duration;
      if (duration < 0.033) {
        duration = 0.033;
      }
      writeArray[0] = duration;
      writeArrays.push(writeArray.buffer);

      //console.log('Frame', index, 'duration', duration);

      var writeArray = new Uint32Array(maxRow * maxColumn);
      for (var i = 0; i < maxRow; ++i) {
        for (var j = 0; j < maxColumn; ++j) {
          var color = frame.Colors[i][j];
          writeArray[i * maxColumn + j] = color;
        }
      }
      writeArrays.push(writeArray.buffer);
    }

    var blob = new Blob(writeArrays, {type: 'application/octet-stream'});

    return blob;
}

Следующий шаг - заставить браузер предложить пользователю загрузить этот BLOB-объект с заранее заданным именем.

Все, что мне было нужно, это именованная ссылка, которую я добавил в HTML5, и которую я мог использовать повторно, чтобы переименовать исходное имя файла. Я скрыл это, так как ссылка не нуждается в отображении.

<a id="lnkDownload" style="display: none" download="client.chroma" href="" target="_blank"></a>

Последний шаг - предложить пользователю загрузить файл.

var data = animation.saveAnimation();
var uriContent = URL.createObjectURL(data);
var lnkDownload = document.getElementById('lnkDownload');
lnkDownload.download = 'theDefaultFileName.extension';
lnkDownload.href = uriContent;
lnkDownload.click();
tgraupmann
источник
1

Вот учебник для экспорта файлов в формате ZIP:

Прежде чем начать, есть библиотека для сохранения файлов, имя библиотеки fileSaver.js, вы можете найти эту библиотеку здесь. Давайте начнем, Теперь, включите необходимые библиотеки:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.4/jszip.min.js"  type="text/javascript"></script>
<script type="text/javascript" src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.js" ></script>

Теперь скопируйте этот код, и этот код загрузит zip-файл с файлом hello.txt, имеющим содержимое Hello World. Если все работает нормально, файл загрузится.

<script type="text/javascript">
    var zip = new JSZip();
    zip.file("Hello.txt", "Hello World\n");
    zip.generateAsync({type:"blob"})
    .then(function(content) {
        // see FileSaver.js
        saveAs(content, "file.zip");
    });
</script>

Это загрузит файл с именем file.zip. Вы можете прочитать больше здесь: http://www.wapgee.com/story/248/guide-to-create-zip-files-using-javascript-by-using-jszip-library

Ильяс Карим
источник