Comment créer un fichier en mémoire pour que l'utilisateur puisse le télécharger, mais pas via le serveur?

827

Existe-t-il un moyen de créer un fichier texte côté client et d'inviter l'utilisateur à le télécharger, sans aucune interaction avec le serveur? Je sais que je ne peux pas écrire directement sur leur machine (sécurité et tout), mais puis-je créer et leur demander de l'enregistrer?

Joseph Silber
la source

Réponses:

432

Vous pouvez utiliser des URI de données. La prise en charge du navigateur varie; voir Wikipedia . Exemple:

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

Le flux d'octets doit forcer une invite de téléchargement. Sinon, il s'ouvrira probablement dans le navigateur.

Pour CSV, vous pouvez utiliser:

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

Essayez la démo jsFiddle .

Matthew Flaschen
la source
19
Ce n'est pas une solution multi-navigateurs mais certainement quelque chose qui mérite d'être étudié. Par exemple, IE limite la prise en charge des données uri. IE 8 limite la taille à 32 Ko et IE 7 et inférieur ne prend pas du tout en charge.
Darin Dimitrov
8
dans Chrome version 19.0.1084.46, cette méthode génère l'avertissement suivant: "Ressource interprétée comme Document mais transférée avec le type MIME text / csv:" data: text / csv, field1% 2Cfield2% 0Afoo% 2Cbar% 0Agoo% 2Cgai% 0A ". " Un téléchargement n'est pas déclenché
Chris
2
Il fonctionne maintenant dans Chrome (testé contre v20 et v21) mais pas IE9 (cela pourrait être juste le jsFiddle, mais en quelque sorte j'en doute).
oreillette
5
Le jeu de caractères correct est presque certainement UTF-16, sauf si vous avez du code pour le convertir en UTF-8. JavaScript utilise UTF-16 en interne. Si vous avez un texte ou un fichier CSV, démarrez la chaîne avec '\ ufeff', la marque d'ordre des octets pour UTF-16BE et les éditeurs de texte pourront lire correctement les caractères non ASCII.
larspars
14
Ajoutez simplement l'attribut download = "txt.csv" afin d'avoir le nom de fichier et l'extension appropriés et de dire à votre système d'exploitation quoi en faire.
elshnkhll
726

Solution simple pour les navigateurs compatibles 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>

Usage

download('test.txt', 'Hello world!');
Matěj Pokorný
la source
10
Oui. C'est exactement ce que @MatthewFlaschen a publié ici il y a environ 3 ans .
Joseph Silber
48
Oui, mais avec l' downloadattribut, vous pouvez spécifier le nom du fichier ;-)
Matěj Pokorný
2
Comme @earcam l'a déjà souligné dans les commentaires ci-dessus .
Joseph Silber
4
Chrome ajoute uniquement l' txtextension si vous ne fournissez pas d'extension dans le nom de fichier. Si vous le faites, download("data.json", data)cela fonctionnera comme prévu.
Carl Smith
6
Cela a fonctionné pour moi dans Chrome (73.0.3683.86) et Firefox (66.0.2). Cela ne fonctionnait PAS dans IE11 (11.379.17763.0) et Edge (44.17763.1.0).
Sam
231

Toutes les solutions ci-dessus ne fonctionnaient pas dans tous les navigateurs. Voici ce qui fonctionne finalement sur IE 10+, Firefox et Chrome (et sans jQuery ni aucune autre bibliothèque):

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

Notez que, selon votre situation, vous pouvez également appeler URL.revokeObjectURL après la suppression elem. Selon les documents pour URL.createObjectURL :

Chaque fois que vous appelez createObjectURL (), une nouvelle URL d'objet est créée, même si vous en avez déjà créé une pour le même objet. Chacun d'eux doit être libéré en appelant URL.revokeObjectURL () lorsque vous n'en avez plus besoin. Les navigateurs les publieront automatiquement lorsque le document sera déchargé; cependant, pour des performances et une utilisation de la mémoire optimales, s'il y a des moments sûrs où vous pouvez les décharger explicitement, vous devez le faire.

Ludovic Feltz
la source
7
Mille mercis. J'ai essayé tous les exemples répertoriés ici et seul celui-ci fonctionne avec n'importe quel navigateur. Ce devrait être la réponse acceptée.
LEM
Dans Chrome, je n'avais pas vraiment besoin d'ajouter l'élément au corps pour que cela fonctionne.
JackMorrissey
1
Pour les applications AngularJS 1.x, vous pouvez créer un tableau d'URL au fur et à mesure de leur création, puis les nettoyer dans la fonction $ onDestroy du composant. Cela fonctionne très bien pour moi.
Splaktar
1
D'autres réponses ont conduit à Failed: network errordans Chrome. Celui-ci fonctionne bien.
juniper-
4
Cela a fonctionné pour moi dans Chrome (73.0.3683.86), Firefox (66.0.2), IE11 (11.379.17763.0) et Edge (44.17763.1.0).
Sam
185

Tous les exemples ci-dessus fonctionnent très bien dans Chrome et IE, mais échouent dans Firefox. Veuillez envisager d'ajouter une ancre au corps et de la retirer après avoir cliqué.

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
la source
4
Cependant: il y a un bogue ouvert dans IE 10 (et je l'ai toujours vu dans 11) qui jette "l'accès est refusé" sur la a.click()ligne car il pense que l'URL de blob est d'origine croisée.
Matt
L'URI des données @Matt est d'origine croisée dans certains navigateurs. autant que je sache, pas seulement en msie, mais aussi en chrome. vous pouvez le tester en essayant d'injecter du javascript avec des données uri. Il ne pourra pas accéder à d'autres parties du site ...
inf3rno
7
"Tous les exemples ci-dessus fonctionnent très bien dans Chrome et IE, mais échouent dans Firefox.". Étant donné que l'ordre des réponses peut changer au fil du temps, il est difficile de savoir quelles réponses étaient au-dessus des vôtres lorsque vous avez écrit cela. Pouvez-vous indiquer exactement quelles approches ne fonctionnent pas dans Firefox?
Kevin
120

J'utilise volontiers FileSaver.js . Sa compatibilité est assez bonne (IE10 + et tout le reste), et il est très simple à utiliser:

var blob = new Blob(["some text"], {
    type: "text/plain;charset=utf-8;",
});
saveAs(blob, "thing.txt");
Daniel Buckmaster
la source
Cela fonctionne très bien sur Chrome. Comment autoriser l'utilisateur à spécifier l'emplacement du fichier sur le disque?
gregm
6
Wow, merci pour la bibliothèque facile à utiliser. C'est facilement la meilleure réponse, et qui se soucie des gens qui utilisent HTML <5 de nos jours?
notbad.jpeg
@gregm Je ne suis pas sûr que vous puissiez le faire avec ce plugin.
Daniel Buckmaster
@gregm: Vous voulez dire l'emplacement de téléchargement? Ce n'est pas lié à FileSaver.js, vous devez définir la configuration de votre navigateur afin qu'il demande un dossier avant chaque téléchargement, ou utiliser l' attribut de téléchargement plutôt nouveau sur <a>.
CodeManX
1
Il s'agit d'une excellente solution pour la famille de navigateurs IE 10+. IE ne prend pas encore en charge la balise HTML 5 de téléchargement et les autres solutions de cette page (et d'autres pages SO discutant du même problème) ne fonctionnaient tout simplement pas pour moi. FileSaver ftw!
TMc
22

La méthode suivante fonctionne dans IE11 +, Firefox 25+ et 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>

Voir ceci en action: http://jsfiddle.net/Kg7eA/

Firefox et Chrome prennent en charge l'URI de données pour la navigation, ce qui nous permet de créer des fichiers en naviguant vers un URI de données, tandis qu'IE ne le prend pas en charge à des fins de sécurité.

D'un autre côté, IE a une API pour enregistrer un blob, qui peut être utilisée pour créer et télécharger des fichiers.

dinesh ygv
la source
Je viens d'utiliser jquery pour attacher des événements (onclick et onready) et définir des attributs, ce que vous pouvez également faire avec vanilla JS. La partie principale (window.navigator.msSaveOrOpenBlob) n'a pas besoin de jquery.
dinesh ygv
Il y a toujours la limitation de taille pour l'approche de l'URI de données, n'est-ce pas?
phil
msSaveOrOpenBlob est affiché comme obsolète ici: developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob
eflat
13

Cette solution est extraite directement du référentiel github de tiddlywiki (tiddlywiki.com). J'ai utilisé tiddlywiki dans presque tous les navigateurs et cela fonctionne comme un charme:

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: Télécharger le module d'économiseur

Danielo515
la source
Cela fonctionne très bien sur Chrome, mais pas sur Firefox. Il crée un fichier et le télécharge, mais le fichier est vide. Pas de contenu. Des idées pourquoi? N'ont pas été testés sur IE ...
Narxx
2
sauf que la fonction n'a pas de nom, c'est mon préféré
Yoraco Gonzales
11

Solution qui fonctionne sur IE10: (j'avais besoin d'un fichier csv, mais il suffit de changer le type et le nom de fichier en 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
la source
1
La réponse de Ludovic comprend ce gros, plus le support pour les autres navigateurs.
Dan Dascalescu
10

Si vous voulez simplement convertir une chaîne pour qu'elle soit disponible en téléchargement, vous pouvez essayer ceci en utilisant jQuery.

$('a.download').attr('href', 'data:application/csv;charset=utf-8,' + encodeURI(data));
Meule
la source
1
Les données de scape avec encodeURI peuvent être nécessaires comme je l'ai suggéré ici avant de pouvoir commenter: stackoverflow.com/a/32441536/4928558
atfornes
10

Le package js-file-download depuis github.com/kennethjiang/js-file-download gère les cas marginaux pour la prise en charge du navigateur:

Voir la source pour voir comment il utilise les techniques mentionnées sur cette page.

Installation

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

Usage

import fileDownload from 'js-file-download'

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

fileDownload(data, 'filename.csv', 'text/csv')
Beau Smith
la source
1
Merci - juste testé - fonctionne avec Firefox, Chrome et Edge sur Windows
Brian Burns
8

Comme mentionné précédemment, l' économiseur de fichiers est un excellent package pour travailler avec des fichiers côté client. Mais ce n'est pas bien avec les gros fichiers. StreamSaver.js est une solution alternative (indiquée dans FileServer.js) qui peut gérer des fichiers volumineux:

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
la source
1
L'encodeur de texte est très expérimental en ce moment, je suggère de l'éviter (ou de le polyfiller).
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
la source
1
Quelles sont les différences entre cette approche et la création d'un blob?
Dan Dascalescu
5

Sur la base de la réponse @Rick qui a été vraiment utile.

Vous devez scape la chaîne datasi vous souhaitez la partager de cette façon:

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

`Désolé, je ne peux pas commenter la réponse de @ Rick en raison de ma faible réputation actuelle dans StackOverflow.

Une suggestion de modification a été partagée et rejetée.

atfornes
la source
1
Je n'ai pas pu accepter la suggestion. Étrange ... J'ai mis à jour le code.
Rick
4

Nous pouvons utiliser l' API URL , en particulier URL.createObjectURL () , et l' API Blob pour encoder et télécharger à peu près n'importe quoi.

Si votre téléchargement est petit, cela fonctionne 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 votre téléchargement est énorme, au lieu d'utiliser le DOM, une meilleure façon est de créer un élément de lien avec les paramètres de téléchargement et de déclencher un clic.

Notez que l'élément de lien n'est pas ajouté au document mais le clic fonctionne quand même! Il est possible de créer un téléchargement de plusieurs centaines de Mo de cette façon.

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>


Prime! Téléchargez tous les objets cycliques , évitez les erreurs:

TypeError: valeur d'objet cyclique (Firefox) TypeError: conversion

structure circulaire à JSON (Chrome et Opera) TypeError: Circular

référence dans l'argument valeur non prise en charge (Edge)

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

Sur cet exemple, le téléchargement du document objet en tant que 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
la source
2

Cette fonction ci-dessous a fonctionné.

 private createDownloadableCsvFile(fileName, content) {
   let link = document.createElement("a");
   link.download = fileName;
   link.href = `data:application/octet-stream,${content}`;
   return link;
 }
giapnh
la source
pouvez-vous ouvrir le fichier dans un nouvel onglet en conservant le nom de fichier attribué, mais pas en le téléchargeant, en l'ouvrant simplement dans un onglet?
Carl Kroeger Ihl
1

La méthode suivante fonctionne dans IE10 +, Edge, Opera, FF et 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);
    }
}

Alors, appelez simplement la fonction:

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

Pour moi, cela fonctionnait parfaitement, avec le même nom de fichier et la même extension téléchargés

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

'title' est le nom de fichier avec l' extension c. -à- sample.pdf, waterfall.jpg, etc ..

'file64' est le contenu base64 quelque chose comme ça, c'est-à-dire Ww6IDEwNDAsIFNsaWRpbmdTY2FsZUdyb3VwOiAiR3JvdXAgQiIsIE1lZGljYWxWaXNpdEZsYXRGZWU6IDM1LCBEZW50YWxQYXltZW50UGVyY2VudGFnZTogMjUsIFByb2NlZHVyZVBlcmNlbnQ6IDcwLKCFfSB7IkdyYW5kVG90YWwiOjEwNDAsIlNsaWRpbmdTY2FsZUdyb3VwIjoiR3JvdXAgQiIsIk1lZGljYWxWaXNpdEZsYXRGZWUiOjM1LCJEZW50YWxQYXltZW50UGVyY2VudGFnZSI6MjUsIlByb2NlZHVyZVBlcmNlbnQiOjcwLCJDcmVhdGVkX0J5IjoiVGVycnkgTGVlIiwiUGF0aWVudExpc3QiOlt7IlBhdGllbnRO

abdul
la source
0

J'utiliserais une <a></a>balise puis définirais le href='path'. Ensuite, placez une image entre les <a>éléments afin que je puisse avoir un visuel pour la voir. Si vous le souhaitez, vous pouvez créer une fonction qui changera le hrefafin que ce ne soit plus seulement le même lien mais dynamique.

Donnez également <a>une balise idsi vous souhaitez y accéder avec javascript.

À partir de la version 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>

Maintenant avec 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
la source
-22

Si le fichier contient des données textuelles, une technique que j'utilise est de placer le texte dans un élément textarea et de le faire sélectionner par l'utilisateur (cliquez dans textarea puis ctrl-A) puis copiez puis collez-le dans un éditeur de texte.

HBP
la source
30
J'avais pensé à cela, mais d'un point de vue convivialité, c'est désastreux. De plus, le fichier doit être enregistré avec une extension CSV. Essayez de le dire à vos utilisateurs.
Joseph Silber, le