Como criar um arquivo na memória para o usuário baixar, mas não através do servidor?

825

Existe alguma maneira de criar um arquivo de texto no lado do cliente e solicitar ao usuário que faça o download, sem nenhuma interação com o servidor? Sei que não posso gravar diretamente na máquina (segurança e tudo), mas posso criar e solicitar que eles a salvem?

Joseph Silber
fonte
1
Veja também: JavaScript: criar e salvar arquivo
Henry Woody

Respostas:

432

Você pode usar URIs de dados. O suporte ao navegador varia; veja Wikipedia . Exemplo:

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

O octet-stream é forçar um prompt de download. Caso contrário, provavelmente será aberto no navegador.

Para CSV, você pode usar:

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

Experimente a demonstração do jsFiddle .

Matthew Flaschen
fonte
19
Esta não é uma solução entre navegadores, mas definitivamente algo que vale a pena olhar. Por exemplo, o IE limita o suporte à uri de dados. O IE 8 limita o tamanho a 32 KB e o IE 7 e inferior não suportam nada.
Darin Dimitrov
8
no Chrome versão 19.0.1084.46, esse método gera o seguinte aviso: "Recurso interpretado como documento, mas transferido com o tipo MIME text / csv:" dados: text / csv, campo1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " A descarga é acionada
Chris
2
Ele funciona no Chrome agora (testado nas versões 20 e 21), mas não no IE9 (que pode ser apenas o jsFiddle, mas de alguma forma eu duvido).
earcam
5
O conjunto de caracteres correto é quase certamente UTF-16, a menos que você tenha um código para convertê-lo em UTF-8. JavaScript usa UTF-16 internamente. Se você tiver um arquivo de texto ou CSV, inicie a string com '\ ufeff', a Marca de Pedido de Byte para UTF-16BE e os editores de texto poderão ler caracteres não ASCII corretamente.
Larspars
14
Basta adicionar o atributo download = "txt.csv" para obter o nome e a extensão adequados do arquivo e informar ao seu sistema operacional o que fazer com ele.
precisa saber é o seguinte
724

Solução simples para navegadores prontos para 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>

Uso

download('test.txt', 'Hello world!');
Matěj Pokorný
fonte
10
Sim. Foi exatamente isso que o @MatthewFlaschen postou aqui há cerca de 3 anos .
23813 Joseph Silber
48
Sim, mas com o downloadatributo você pode especificar o nome do arquivo ;-)
Matěj Pokorný
2
Como @earcam já apontou nos comentários acima .
Joseph Silber
4
O Chrome anexa apenas a txtextensão se você não fornecer uma extensão no nome do arquivo. Se você fizer download("data.json", data), funcionará como esperado.
Carl Smith
6
Isso funcionou para mim no Chrome (73.0.3683.86) e no Firefox (66.0.2). Ele tinha NÃO trabalhar em IE11 (11.379.17763.0) e Edge (44.17763.1.0).
29519 Sam
231

Todas as soluções acima não funcionaram em todos os navegadores. Aqui está o que finalmente funciona no IE 10+, Firefox e Chrome (e sem o jQuery ou qualquer outra biblioteca):

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

Observe que, dependendo da sua situação, convém chamar URL.revokeObjectURL após a remoção elem. De acordo com os documentos para URL.createObjectURL :

Cada vez que você chama createObjectURL (), um novo URL de objeto é criado, mesmo que você já tenha criado um para o mesmo objeto. Cada um deles deve ser liberado chamando URL.revokeObjectURL () quando você não precisar mais deles. Os navegadores as liberam automaticamente quando o documento é descarregado; no entanto, para otimizar o desempenho e o uso da memória, se houver momentos seguros em que você possa explicitamente descarregá-los, faça-o.

Ludovic Feltz
fonte
7
Graças um milhão. Eu tentei todos os exemplos listados aqui e somente este funciona com qualquer navegador. Essa deve ser a resposta aceita.
LEM 27/05
No Chrome, na verdade, não era necessário anexar o elemento ao corpo para que isso funcionasse.
21416 JackMorrissey #
1
Para aplicativos AngularJS 1.x, você pode criar uma matriz de URLs conforme elas são criadas e depois limpá-las na função $ onDestroy do componente. Isso está funcionando muito bem para mim.
Splaktar 6/09/16
1
Outras respostas levaram ao Failed: network errorChrome. Este funciona bem.
precisa saber é o seguinte
4
Isso funcionou para mim no Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) e Edge (44.17763.1.0).
29519 Sam
185

Todo o exemplo acima funciona bem no Chrome e no IE, mas falha no Firefox. Por favor, considere anexar uma âncora ao corpo e removê-la após clicar.

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
fonte
4
No entanto: existe um bug aberto no IE 10 (e eu ainda o vi no 11) que lança "Acesso negado" na a.click()linha porque acha que o URL do blob é de origem cruzada.
16163 Matt
O uri de dados do @Matt tem origem cruzada em alguns navegadores. até onde eu sei, não apenas no msie, mas também no chrome. você pode testá-lo tentando injetar javascript com uri de dados. Não será capaz de acessar outras partes do site ...
inf3rno 13/09/2015
7
"Todo o exemplo acima funciona bem no chrome e no IE, mas falha no Firefox.". Como a ordem das respostas pode mudar com o tempo, não está claro quais respostas estavam acima das suas quando você escreveu isso. Você pode indicar exatamente quais abordagens não funcionam no Firefox?
Kevin
120

Estou feliz usando o FileSaver.js . Sua compatibilidade é muito boa (IE10 + e tudo mais) e é muito simples de usar:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");
Daniel Buckmaster
fonte
Isso funciona muito bem no Chrome. Como permito que o usuário especifique o local do arquivo no disco?
Gregm
6
Uau, obrigado pela biblioteca fácil de usar. Essa é facilmente a melhor resposta, e quem se importa com as pessoas que usam HTML <5 atualmente?
notbad.jpeg
@gregm Não tenho certeza se você pode com este plugin.
Daniel Buckmaster
@gregm: Você quer dizer o local do download? Isso não está relacionado ao FileSaver.js, você precisa definir a configuração do navegador para solicitar uma pasta antes de cada download ou usar o novo atributo de download em <a>.
CodeManX
1
Esta é uma ótima solução para a família de navegadores IE 10+. O IE ainda não suporta a tag HTML 5 de download e as outras soluções nesta página (e outras páginas SO que discutem o mesmo problema) simplesmente não estavam funcionando para mim. FileSaver ftw!
TMc 12/01
22

O método a seguir funciona no IE11 +, Firefox 25+ e 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>

Veja isso em ação: http://jsfiddle.net/Kg7eA/

O Firefox e o Chrome oferecem suporte ao URI de dados para navegação, o que nos permite criar arquivos navegando para um URI de dados, enquanto o IE não o suporta por motivos de segurança.

Por outro lado, o IE possui API para salvar um blob, que pode ser usado para criar e baixar arquivos.

dinesh ygv
fonte
Eu apenas usei o jquery para anexar eventos (onclick e onready) e definir atributos, o que você também pode fazer com o vanilla JS. A parte principal (window.navigator.msSaveOrOpenBlob) não precisa de jquery.
dinesh ygv
Ainda existe a limitação de tamanho para a abordagem de uri de dados, não é?
26417 phil phil
msSaveOrOpenBlob é mostrado como obsoleto aqui: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat
13

Essa solução é extraída diretamente do repositório github do tiddlywiki (tiddlywiki.com). Eu usei o tiddlywiki em quase todos os navegadores e funciona como um encanto:

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

Repo Github: Download do módulo de proteção

Danielo515
fonte
Funciona muito bem no Chrome, mas não no Firefox. Ele cria e baixa um arquivo, mas o arquivo está vazio. Sem conteúdo. Alguma idéia do porquê? Não testei no IE ...
Narxx
2
exceto que a função não tem nome, este é o meu favorito
Yoraco Gonzales
11

Solução que funciona no IE10: (eu precisava de um arquivo csv, mas basta alterar o tipo e o nome do arquivo para 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
fonte
1
A resposta de Ludovic inclui esse grande suporte adicional para os outros navegadores.
Dan Dascalescu 5/11
10

Se você quiser apenas converter uma string para estar disponível para download, tente isso usando o jQuery.

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));
Rick
fonte
1
Dados Scape com encodeURI pode ser necessária como sugeri aqui antes de poder comentário: stackoverflow.com/a/32441536/4928558
atfornes
10

O pacote js-file-download em github.com/kennethjiang/js-file-download lida com casos extremos para suporte ao navegador:

Ver fonte - para ver como ele usa as técnicas mencionadas nesta página.

Instalação

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

Uso

import fileDownload from 'js-file-download'

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

fileDownload(data, 'filename.csv', 'text/csv')
Beau Smith
fonte
1
Graças - apenas testadas - Funciona com Firefox, Chrome e Edge no Windows
Brian Burns
8

Como mencionado anteriormente, o filesaver é um ótimo pacote para trabalhar com arquivos no lado do cliente. Mas, não é bem feito com arquivos grandes. StreamSaver.js é uma solução alternativa (apontada em FileServer.js) que pode manipular arquivos grandes:

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
fonte
1
O Text Encoder é altamente experimental no momento, eu sugiro evitá-lo (ou polifilling).
precisa saber é o seguinte
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
fonte
1
Quais são as diferenças entre essa abordagem e a criação de um Blob?
Dan Dascalescu
5

Com base na resposta @Rick, que foi realmente útil.

Você precisa escapar da string datase quiser compartilhá-la desta maneira:

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

`Desculpe, não posso comentar a resposta de @ Rick devido à minha baixa reputação atual no StackOverflow.

Uma sugestão de edição foi compartilhada e rejeitada.

atfornes
fonte
1
Não pude aceitar a sugestão. Estranho ... Eu atualizei o código.
Rick
4

Podemos usar a URL api, em particular URL.createObjectURL () , eo Blob API para codificar e baixar praticamente qualquer coisa.

Se o seu download for pequeno, isso funcionará bem:

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


Se o seu download for grande, em vez de usar o DOM, a melhor maneira é criar um elemento de link com os parâmetros de download e acionar um clique.

Observe que o elemento link não está anexado ao documento, mas o clique funciona mesmo assim! É possível criar um download de muitas centenas de Mo dessa maneira.

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>


Bônus!Faça o download de qualquer objeto cíclico , evite os erros:

TypeError: valor do objeto cíclico (Firefox) TypeError: Convertendo

estrutura circular para JSON (Chrome e Opera) TypeError: Circular

referência no argumento do valor não suportada (Edge)

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

Neste exemplo, baixando o documentobjeto como 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
fonte
2

Esta função abaixo funcionou.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }
giapnh
fonte
você pode abrir o arquivo em uma nova guia, mantendo o nome do arquivo atribuído, mas não o download, apenas abrindo em uma guia?
Carl Kroeger Ihl 5/12/19
1

O método a seguir funciona no IE10 +, Edge, Opera, FF e 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);
    }
}

Então, basta chamar a função:

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

Para mim, isso funcionou perfeitamente, com o mesmo nome de arquivo e extensão sendo baixados

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

'title' é o nome do arquivo com extensão, ou seja sample.pdf,waterfall.jpg , etc ..

'file64' é o conteúdo base64, algo como isto, ou seja, Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO

abdicar
fonte
0

Eu usaria uma <a></a>tag e depois definiria href='path'. Posteriormente, coloque uma imagem entre os <a>elementos para que eu possa ter um visual para vê-la. Se você quiser, poderá criar uma função que alterará ohref para que ela não seja apenas o mesmo link, mas seja dinâmica.

<a>uma tag idtambém se você quiser acessá-la com javascript.

Começando com a versão 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>

Agora com 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
fonte
-22

Se o arquivo contiver dados de texto, uma técnica que eu uso é colocar o texto em um elemento da área de texto e fazer com que o usuário o selecione (clique em área de texto e, em seguida, ctrl-A) e copie seguido de uma colagem em um editor de texto.

HBP
fonte
30
Eu havia considerado isso, mas do ponto de vista do usuário, isso é desastroso. Além disso, o arquivo deve ser salvo com uma extensão CSV. Tente dizer isso aos seus usuários.
Joseph Silber