Nombre de archivo de blob de JavaScript sin enlace

189

¿Cómo se configura el nombre de un archivo de blob en JavaScript cuando se fuerza la descarga a través de window.location?

function newFile(data) {
    var json = JSON.stringify(data);
    var blob = new Blob([json], {type: "octet/stream"});
    var url  = window.URL.createObjectURL(blob);
    window.location.assign(url);
}

Al ejecutar el código anterior, se descarga un archivo al instante sin una actualización de página que se vea así:

bfefe410-8d9c-4883-86c5-d76c50a24a1d

En su lugar, quiero establecer el nombre del archivo como my-download.json .

Azul ceniza
fuente

Respuestas:

313

La única forma en que soy consciente es el truco utilizado por FileSaver.js :

  1. Crea una <a>etiqueta oculta .
  2. Establezca su hrefatributo en la URL del blob.
  3. Establezca su downloadatributo al nombre del archivo.
  4. Haga clic en la <a>etiqueta

Aquí hay un ejemplo simplificado ( jsfiddle ):

var saveData = (function () {
    var a = document.createElement("a");
    document.body.appendChild(a);
    a.style = "display: none";
    return function (data, fileName) {
        var json = JSON.stringify(data),
            blob = new Blob([json], {type: "octet/stream"}),
            url = window.URL.createObjectURL(blob);
        a.href = url;
        a.download = fileName;
        a.click();
        window.URL.revokeObjectURL(url);
    };
}());

var data = { x: 42, s: "hello, world", d: new Date() },
    fileName = "my-download.json";

saveData(data, fileName);

Escribí este ejemplo solo para ilustrar la idea, en el código de producción use FileSaver.js en su lugar.

Notas

  • Los navegadores más antiguos no admiten el atributo "descargar", ya que es parte de HTML5.
  • Algunos formatos de archivo son considerados inseguros por el navegador y la descarga falla. Guardar archivos JSON con extensión txt me funciona.
kol
fuente
2
@AshBlue El atributo "descargar" necesita HTML5. Mi código es solo un ejemplo, también puede probar la página de demostración de FileSaver.js: eligrey.com/demos/FileSaver.js
kol
1
Curiosamente, si intenta descargar repetidamente un txt de esta manera (presionando el botón Ejecutar en jsfiddle.net una y otra vez), la descarga a veces falla.
kol
2
Solo quería mencionar que esta solución no funcionará para archivos con tamaños superiores a un umbral particular. por ejemplo,> 2 MB para cromo. Este tamaño varía de un navegador a otro.
manojadams
3
Esto no funciona para mí porque necesito abrir el archivo en una pestaña nueva. Tengo que mostrar un PDF en Chrome, pero necesito mostrar un nombre fácil de usar en la barra de herramientas de URL, y si el usuario quiere descargar a través del icono de descarga, tengo que poner el mismo nombre fácil de usar en el archivo.
Adrian Paredes
1
Solo para agregar, no necesita montar realmente una etiqueta en el cuerpo para que esto funcione (intentado ahora en Chrome)
más allá del código del
52

Solo quería ampliar la respuesta aceptada con soporte para Internet Explorer (la mayoría de las versiones modernas, de todos modos), y ordenar el código usando jQuery:

$(document).ready(function() {
    saveFile("Example.txt", "data:attachment/text", "Hello, world.");
});

function saveFile (name, type, data) {
    if (data !== null && navigator.msSaveBlob)
        return navigator.msSaveBlob(new Blob([data], { type: type }), name);
    var a = $("<a style='display: none;'/>");
    var url = window.URL.createObjectURL(new Blob([data], {type: type}));
    a.attr("href", url);
    a.attr("download", name);
    $("body").append(a);
    a[0].click();
    window.URL.revokeObjectURL(url);
    a.remove();
}

Aquí hay un ejemplo de Fiddle . Godspeed .

Alexandru
fuente
Funcionó perfectamente.
N8allan
1
¡Utilicé la solución aceptada pero no funcionó en firefox! Aún no sé por qué. Su solución funcionó en firefox. Gracias.
elahehab
@elahehab Mis soluciones siempre funcionan;)
Alexandru
27

El mismo principio que las soluciones anteriores. Pero tuve problemas con Firefox 52.0 (32 bits) donde los archivos grandes (> 40 MB) se truncan en posiciones aleatorias. Reprogramar la llamada de revokeObjectUrl () soluciona este problema.

function saveFile(blob, filename) {
  if (window.navigator.msSaveOrOpenBlob) {
    window.navigator.msSaveOrOpenBlob(blob, filename);
  } else {
    const a = document.createElement('a');
    document.body.appendChild(a);
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = filename;
    a.click();
    setTimeout(() => {
      window.URL.revokeObjectURL(url);
      document.body.removeChild(a);
    }, 0)
  }
}

Ejemplo de jsfiddle

Kim Nyholm
fuente
1
Descubrí que este truco setTimeout () corrige MS Edge, donde el archivo no se descargaba en absoluto. Sin embargo, solo la llamada a revokeObjectURL () debe retrasarse.
Russell Phillips
Descubrí que el "if (window.navigator.msSaveOrOpenBlob)" es lo que me funcionó
Jacques Olivier el
23

Tarde, pero como tuve el mismo problema agrego mi solución:

function newFile(data, fileName) {
    var json = JSON.stringify(data);
    //IE11 support
    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
        let blob = new Blob([json], {type: "application/json"});
        window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else {// other browsers
        let file = new File([json], fileName, {type: "application/json"});
        let exportUrl = URL.createObjectURL(file);
        window.location.assign(exportUrl);
        URL.revokeObjectURL(exportUrl);
    }
}
ben
fuente
55
Gracias @ben. Esto está funcionando bien. Sin elementos dom, nada como disparar como un evento de clic. Simplemente funciona increíble con la extensión adecuada. Pero no se considera el nombre de archivo dado, descargando "<object_url_id> .csv" en lugar de "<myfileName> .csv"
Ram Babu S
3
Llamar revokeObjectURLdespués location.assignfunciona bien en Firefox, pero interrumpe la descarga en Chrome.
Fred
Tenga en cuenta que "Edge no es compatible con el constructor de archivos". Árbitro. caniuse.com/#feat=fileapi
user1477388 el
Esta debería ser la respuesta correcta. No tiene sentido crear objetos inútiles en el árbol DOM
Luiz Felipe
Ahora sí, desde enero '20
Luiz Felipe
6
saveFileOnUserDevice = function(file){ // content: blob, name: string
        if(navigator.msSaveBlob){ // For ie and Edge
            return navigator.msSaveBlob(file.content, file.name);
        }
        else{
            let link = document.createElement('a');
            link.href = window.URL.createObjectURL(file.content);
            link.download = file.name;
            document.body.appendChild(link);
            link.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true, view: window}));
            link.remove();
            window.URL.revokeObjectURL(link.href);
        }
    }
Jean-Philippe
fuente
¿Hay alguna manera de abrir una nueva ventana?
Enrique Altuna
Creo que puede llamar en link.click()lugar de enviar un evento de mouse.
Fred
2

Ejemplo de trabajo de un botón de descarga, para guardar una foto de gato de una url como "cat.jpg":

HTML:

<button onclick="downloadUrl('https://i.imgur.com/AD3MbBi.jpg', 'cat.jpg')">Download</button>

JavaScript:

function downloadUrl(url, filename) {
  let xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "blob";
  xhr.onload = function(e) {
    if (this.status == 200) {
      const blob = this.response;
      const a = document.createElement("a");
      document.body.appendChild(a);
      const blobUrl = window.URL.createObjectURL(blob);
      a.href = blobUrl;
      a.download = filename;
      a.click();
      setTimeout(() => {
        window.URL.revokeObjectURL(blobUrl);
        document.body.removeChild(a);
      }, 0);
    }
  };
  xhr.send();
}
usuario1032613
fuente
1

window.location.assign no funcionó para mí. se descarga bien pero se descarga sin una extensión para un archivo CSV en la plataforma Windows. Lo siguiente funcionó para mí.

    var blob = new Blob([csvString], { type: 'text/csv' });
    //window.location.assign(window.URL.createObjectURL(blob));
    var link = window.document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    // Construct filename dynamically and set to link.download
    link.download = link.href.split('/').pop() + '.' + extension; 
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
Sacky San
fuente
0

Esta es mi solución Desde mi punto de vista, no puedes pasar por alto el <a>.

function export2json() {
  const data = {
    a: '111',
    b: '222',
    c: '333'
  };
  const a = document.createElement("a");
  a.href = URL.createObjectURL(
    new Blob([JSON.stringify(data, null, 2)], {
      type: "application/json"
    })
  );
  a.setAttribute("download", "data.json");
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}
<button onclick="export2json()">Export data to json file</button>

dabeng
fuente