¿Cómo crear un archivo en la memoria para que el usuario lo descargue, pero no a través del servidor?

Respuestas:

432

Puede usar URI de datos. El soporte del navegador varía; ver Wikipedia . Ejemplo:

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

El flujo de octetos es forzar una solicitud de descarga. De lo contrario, probablemente se abrirá en el navegador.

Para CSV, puede usar:

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

Prueba la demo jsFiddle .

Matthew Flaschen
fuente
19
Esta no es una solución de navegador cruzado, pero definitivamente es algo que vale la pena mirar. Por ejemplo, IE limita el soporte a datos uri. IE 8 limita el tamaño a 32 KB e IE 7 y versiones inferiores no son compatibles en absoluto.
Darin Dimitrov
8
en la versión 19.0.1084.46 de Chrome, este método genera la siguiente advertencia: "Recurso interpretado como documento pero transferido con el tipo MIME text / csv:" data: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " No se activa una descarga
Chris
2
Ahora funciona en Chrome (probado en v20 y v21) pero no en IE9 (podría ser el jsFiddle, pero de alguna manera lo dudo).
Earcam
55
El juego de caracteres correcto es casi seguro UTF-16, a menos que tenga un código que lo convierta a UTF-8. JavaScript usa UTF-16 internamente. Si tiene un archivo de texto o CSV, comience la cadena con '\ ufeff', la marca de orden de bytes para UTF-16BE, y los editores de texto podrán leer caracteres no ASCII correctamente.
Larspars
14
Simplemente agregue el atributo download = "txt.csv" para tener el nombre y la extensión de archivo adecuados y decirle a su sistema operativo qué hacer con él.
elshnkhll
726

Solución simple para navegadores listos 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ý
fuente
10
Sí. Esto es exactamente lo que @MatthewFlaschen ha publicado aquí hace aproximadamente 3 años .
Joseph Silber
48
Sí, pero con el downloadatributo puede especificar el nombre del archivo ;-)
Matěj Pokorný
2
Como @earcam ya ha señalado en los comentarios anteriores .
Joseph Silber
44
Chrome solo agrega la txtextensión si no proporciona una extensión en el nombre del archivo. Si lo haces download("data.json", data), funcionará como se espera.
Carl Smith
66
Esto funcionó para mí en Chrome (73.0.3683.86) y Firefox (66.0.2). No pasó NO funciona en IE11 (11.379.17763.0) y Edge (44.17763.1.0).
Sam
231

Todas las soluciones anteriores no funcionaron en todos los navegadores. Esto es lo que finalmente funciona en IE 10+, Firefox y Chrome (y sin jQuery o cualquier otra 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);
    }
}

Tenga en cuenta que, según su situación, también puede llamar a URL.revokeObjectURL después de eliminarlo elem. Según los documentos para URL.createObjectURL :

Cada vez que llama a createObjectURL (), se crea una nueva URL de objeto, incluso si ya ha creado una para el mismo objeto. Cada uno de estos debe liberarse llamando a URL.revokeObjectURL () cuando ya no los necesite. Los navegadores los liberarán automáticamente cuando se descargue el documento; sin embargo, para un rendimiento óptimo y uso de memoria, si hay momentos seguros en los que puede descargarlos explícitamente, debe hacerlo.

Ludovic Feltz
fuente
77
Un millón de gracias. He probado todos los ejemplos enumerados aquí y solo este funciona con cualquier navegador. Esta debería ser la respuesta aceptada.
LEM
En Chrome, en realidad no tuve que agregar el elemento al cuerpo para que esto funcione.
JackMorrissey
1
Para las aplicaciones AngularJS 1.x, puede crear una matriz de URL a medida que se crean y luego limpiarlas en la función $ onDestroy del componente. Esto está funcionando muy bien para mí.
Splaktar
1
Otras respuestas condujeron a Failed: network erroren Chrome. Este funciona bien.
enebro-
44
Esto funcionó para mí en Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) y Edge (44.17763.1.0).
Sam
185

Todo el ejemplo anterior funciona bien en Chrome e IE, pero falla en Firefox. Considere agregar un ancla al cuerpo y eliminarlo después de hacer clic.

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
fuente
44
Sin embargo: hay un error abierto en IE 10 (y todavía lo he visto en 11) que arroja "Acceso denegado" en la a.click()línea porque cree que la URL del blob es de origen cruzado.
Matt
@Matt data uri es de origen cruzado en algunos navegadores. que yo sepa, no solo en msie, sino también en cromo. puede probarlo intentando inyectar javascript con datos uri. No podrá acceder a otras partes del sitio ...
inf3rno
77
"Todo el ejemplo anterior funciona bien en Chrome e IE, pero falla en Firefox". Dado que el orden de las respuestas puede cambiar con el tiempo, no está claro qué respuestas estaban por encima de las suyas cuando escribió esto. ¿Puedes indicar exactamente qué enfoques no funcionan en Firefox?
Kevin
120

Estoy feliz de usar FileSaver.js . Su compatibilidad es bastante buena (IE10 + y todo lo demás), y es muy simple de usar:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");
Daniel Buckmaster
fuente
Esto funciona muy bien en Chrome. ¿Cómo le permito al usuario especificar la ubicación del archivo en el disco?
gregm
66
Wow, gracias por la biblioteca fácil de usar. Esta es fácilmente la mejor respuesta, y ¿a quién le importa la gente que usa HTML <5 en estos días?
notbad.jpeg
@gregm No estoy seguro de que puedas con este complemento.
Daniel Buckmaster
@gregm: ¿Te refieres a la ubicación de descarga? Que no está relacionado con FileSaver.js, es necesario establecer la configuración de su navegador para que le pide una carpeta antes de cada descarga, o bien utilizar el nuevo atributo de descarga en <a>.
CodeManX
1
Esta es una GRAN solución para la familia de navegadores IE 10+. IE aún no admite la descarga de la etiqueta HTML 5 y las otras soluciones en esta página (y otras páginas SO que discuten el mismo problema) simplemente no funcionaban para mí. FileSaver ftw!
TMc
22

El siguiente método funciona en IE11 +, Firefox 25+ y 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>

Vea esto en acción: http://jsfiddle.net/Kg7eA/

Firefox y Chrome admiten URI de datos para la navegación, lo que nos permite crear archivos navegando a un URI de datos, mientras que IE no lo admite por motivos de seguridad.

Por otro lado, IE tiene una API para guardar un blob, que se puede usar para crear y descargar archivos.

dinesh ygv
fuente
Acabo de usar jquery para adjuntar eventos (onclick y onready) y establecer atributos, lo que también puede hacer con vanilla JS. La parte central (window.navigator.msSaveOrOpenBlob) no necesita jquery.
dinesh ygv
Todavía existe la limitación de tamaño para el enfoque uri de datos, ¿no?
phil
msSaveOrOpenBlob se muestra como obsoleto aquí: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat
13

Esta solución se extrae directamente del repositorio github de tiddlywiki (tiddlywiki.com). He usado tiddlywiki en casi todos los navegadores y funciona de maravilla:

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

Repositorio de Github: descargar el módulo de ahorro

Danielo515
fuente
Funciona muy bien en Chrome, pero no en Firefox. Realiza un archivo y lo descarga, pero el archivo está vacío. Sin contenido. Alguna idea de por qué? No he probado en IE ...
Narxx
2
excepto que la función no tiene nombre, este es mi favorito
Yoraco Gonzales
11

Solución que funciona en IE10: (necesitaba un archivo csv, pero es suficiente para cambiar el tipo y el nombre de archivo a 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
fuente
1
La respuesta de Ludovic incluye este gran soporte para los otros navegadores.
Dan Dascalescu
10

Si solo desea convertir una cadena para que esté disponible para descargar, puede probar esto usando jQuery.

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));
Almiar
fuente
1
Es posible que se necesiten datos de escape con encodeURI como sugerí aquí antes de poder comentar: stackoverflow.com/a/32441536/4928558
atfornes
10

El paquete js-file-download de github.com/kennethjiang/js-file-download maneja casos extremos para el soporte del navegador:

Vea la fuente para ver cómo usa las técnicas mencionadas en esta página.

Instalación

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
fuente
1
Gracias, recién probado, funciona con Firefox, Chrome y Edge en Windows
Brian Burns el
8

Como se mencionó anteriormente, el protector de archivos es un excelente paquete para trabajar con archivos en el lado del cliente. Pero, no le va bien con archivos grandes. StreamSaver.js es una solución alternativa (que se señala en FileServer.js) que puede manejar archivos 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
fuente
1
Text Encoder es altamente experimental en este momento, sugiero evitarlo (o rellenarlo).
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();
MANVENDRA LODHI
fuente
1
¿Cuáles son las diferencias entre este enfoque y la creación de un Blob?
Dan Dascalescu
5

Basado en la respuesta de @Rick, que fue realmente útil.

Tienes que escapar de la cadena datasi quieres compartirla de esta manera:

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

`Lo siento, no puedo comentar sobre la respuesta de @ Rick debido a mi baja reputación actual en StackOverflow.

Una sugerencia de edición fue compartida y rechazada.

atfornes
fuente
1
No pude aceptar la sugerencia. Extraño ... Actualicé el código.
Rick
4

Podemos utilizar la dirección URL de la API, en particular, URL.createObjectURL () , y el Blob API para codificar y descarga casi cualquier cosa.

Si su descarga es pequeña, esto funciona bien:

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


Si su descarga es enorme, en lugar de usar el DOM, una mejor manera es crear un elemento de enlace con los parámetros de descarga y activar un clic.

¡Observe que el elemento de enlace no se agrega al documento, pero el clic funciona de todos modos! Esto es posible para crear una descarga de muchos cientos de Mo de esta manera.

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>


¡Prima! Descargue cualquier objeto cíclico , evite los errores:

TypeError: valor de objeto cíclico (Firefox) TypeError: Convertir

estructura circular a JSON (Chrome y Opera) TypeError: Circular

referencia en argumento de valor no compatible (Edge)

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

En este ejemplo, descargando el 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
fuente
2

Esta siguiente función funcionó.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }
giapnh
fuente
¿puedes abrir el archivo en una nueva pestaña manteniendo el nombre de archivo asignado, pero no descargando, solo abriendo en una pestaña?
Carl Kroeger Ihl
1

El siguiente método funciona en IE10 +, Edge, Opera, FF y 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);
    }
}

Entonces, solo llame a la función:

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

Para mí, esto funcionó perfectamente, con el mismo nombre de archivo y extensión descargándose

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

'título' es el nombre del archivo con la extensión es decir, sample.pdf, waterfall.jpg, etc ..

'file64' es el contenido de base64 algo como esto, es decir, Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO

abdul
fuente
0

Usaría una <a></a>etiqueta y luego establecería el href='path'. Luego, coloque una imagen entre los <a>elementos para que pueda tener una imagen visual para verla. Si lo desea, puede crear una función que cambie la función hrefpara que no solo sea el mismo enlace, sino que sea dinámica.

Dele también la <a>etiqueta idsi desea acceder a ella con javascript.

Comenzando con la versió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>

Ahora con 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
fuente
-22

Si el archivo contiene datos de texto, una técnica que uso es colocar el texto en un elemento de área de texto y hacer que el usuario lo seleccione (haga clic en área de texto y luego ctrl-A) y luego copie y pegue en un editor de texto.

HBP
fuente
30
Lo había considerado, pero desde un punto de vista fácil de usar, esto es desastroso. Además, el archivo debe guardarse con una extensión CSV. Intenta decírselo a tus usuarios.
Joseph Silber