Nome do arquivo blob JavaScript sem link

189

Como você define o nome de um arquivo de blob no JavaScript quando forçar o download atravé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);
}

A execução do código acima baixa um arquivo instantaneamente sem uma atualização de página parecida com:

bfefe410-8d9c-4883-86c5-d76c50a24a1d

Quero definir o nome do arquivo como my-download.json .

Cinza Azul
fonte

Respostas:

313

A única maneira de conhecer é o truque usado pelo FileSaver.js :

  1. Crie uma <a>tag oculta .
  2. Defina seu hrefatributo para a URL do blob.
  3. Defina seu downloadatributo para o nome do arquivo.
  4. Clique na <a>etiqueta.

Aqui está um exemplo 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);

Eu escrevi este exemplo apenas para ilustrar a ideia. No código de produção, use o FileSaver.js.

Notas

  • Navegadores mais antigos não oferecem suporte ao atributo "download", pois faz parte do HTML5.
  • Alguns formatos de arquivo são considerados inseguros pelo navegador e o download falha. Salvar arquivos JSON com extensão txt funciona para mim.
kol
fonte
2
@AshBlue O atributo "download" precisa de HTML5. Meu código é apenas um exemplo, você também pode tentar os FileSaver.js página de demonstração: eligrey.com/demos/FileSaver.js
kol
1
Curiosamente, se você tentar repetidamente baixar um txt dessa maneira (pressionando o botão Executar no jsfiddle.net várias vezes), o download às vezes falha.
kol
2
Só queria mencionar que esta solução não funcionará para arquivos com tamanhos maiores que um limite específico. por exemplo,> 2 MB para o chrome. Esse tamanho varia de navegador para navegador.
manojadams
3
Isso não funciona para mim porque eu preciso abrir o arquivo em uma nova guia. Tenho que mostrar um PDF no Chrome, mas preciso mostrar um nome amigável na barra de ferramentas da URL e, se o usuário quiser fazer o download através do ícone de download, tenho que colocar o mesmo nome amigável no arquivo.
Adrian Paredes
1
Só para acrescentar, você não precisa realmente montar a uma tag para o corpo, a fim para que isso funcione (tentei apenas agora no Chrome)
além do código
52

Eu só queria expandir a resposta aceita com suporte para o Internet Explorer (versões mais modernas, enfim) e organizar o código usando o 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();
}

Aqui está um exemplo do Fiddle . Godspeed .

Alexandru
fonte
Funcionou perfeitamente.
N8allan
1
Usei a solução aceita, mas ela não funcionou no firefox! Eu ainda não sei o porquê. Sua solução funcionou no firefox. Obrigado.
elahehab
@elahehab Minhas soluções sempre funcionam;)
Alexandru
27

Mesmo princípio que as soluções acima. Mas tive problemas com o Firefox 52.0 (32 bits) em que arquivos grandes (> 40 MBytes) são truncados em posições aleatórias. Agendar novamente a chamada de revokeObjectUrl () corrige esse 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)
  }
}

exemplo jsfiddle

Kim Nyholm
fonte
1
Eu descobri que esse hack setTimeout () corrige o MS Edge, onde o arquivo não seria baixado. No entanto, apenas a chamada para revokeObjectURL () precisa ser atrasada.
Russell Phillips
Eu achei que o "se (window.navigator.msSaveOrOpenBlob)" é o que fez o truque para mim
Jacques Olivier
23

Tarde, mas como tive o mesmo problema, adicionei minha solução:

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
fonte
5
Obrigado @ ben. Isso está funcionando bem. Sem elementos dom, nada como disparar como evento de clique. Ele simplesmente funciona de maneira impressionante com a extensão adequada. Mas o dado nome do arquivo não é considerado, download "<object_url_id> .csv" em vez de "<myFileName> .csv"
Ram Babu S
3
Ligar revokeObjectURLdepois location.assignfunciona bem no Firefox, mas interrompe o download no Chrome.
Fred
Observe que "O Edge não suporta o construtor File". Ref. caniuse.com/#feat=fileapi
user1477388
Essa deve ser a resposta correta. Não faz sentido criar objetos inúteis na árvore DOM
Luiz Felipe
Agora sim, desde Jan '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
fonte
existe alguma maneira de abrir nele uma nova janela?
Enrique Altuna
Eu acho que você pode ligar em link.click()vez de enviar um evento de mouse.
Fred
2

Exemplo prático de um botão de download, para salvar uma foto de gato de um 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();
}
user1032613
fonte
1

window.location.assign não funcionou para mim. ele baixa bem, mas baixa sem uma extensão para um arquivo CSV na plataforma Windows. O seguinte funcionou para mim.

    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
fonte
0

Esta é a minha solução. Do meu ponto de vista, você não pode ignorar o <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
fonte