JavaScript
HTML5
jQuery

JavaScript でファイル保存・開くダイアログを出して読み書きするまとめ

※2018/09/19: 本質的な内容はそのままですが、記事を大幅に編集しました。jQuery を使ったコードは記事の後ろにまとめました。

ユーザーが <a><input> をクリックしたタイミングではなく、JavaScript のコード中の好きなタイミングでファイル保存・開くダイアログを出す方法のまとめです。
click イベントが発生してから何か処理をした後にダイアログを出したいときなどにも有効です。

「ファイル保存・開くダイアログを出して」としたのは「ファイルを読み書き」だと一時ファイルを読み書きする記事と間違えるそうなためです。

以下、 Document.createElement() で HTML 要素を作成していますが、jQuery を使用したり、もともと display: none; で HTML に記述していても同様にできます。

1. ファイル保存

1.1. 方法1: Data URI を使う

Chrome
const a = document.createElement('a');
a.href = 'data:text/plain,' + encodeURIComponent('test text\n');
a.download = 'test.txt';

a.click();
Firefox, Chrome
const a = document.createElement('a');
a.href = 'data:text/plain,' + encodeURIComponent('test text\n');
a.download = 'test.txt';

a.style.display = 'none';
document.body.appendChild(a); // ※ DOM が構築されてからでないとエラーになる
a.click();
document.body.removeChild(a);

data URI にしたデータを <a> タグのダウンロードリンクにし、イベントを強制発火して保存する方法です。
text や json などのサイズがあまり大きくないテキストファイルを保存したいときなどに使えそうです。

IE, Edge は <a> タグに Data URI を使うと正しく動作しないようです…。
Firefox は DOM 上の <a> タグでないと click() が動作しないようです…。

参考「data URIs | MDN
参考「javascript でテキストをローカルに保存する方法 | TM Life

1.2. 方法2: Blob URL を使う

href の部分を dara URI ではなく Blob URL にしてダウンロードリンクにする方法。
Blob URL の生成には URL.createObjectURL() を使用。

Firefox, Chrome, Edge
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob(['test text\n'], {type: 'text/plain'}));
a.download = 'test.txt';

a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);

IE は <a> タグに Blob URL を使うと正しく動作しないようです…。

(UTF-8 の BOM を付けたいときは new Blob([bom, content], {'type': mime}) とする)

参考「URL.createObjectURL - Web API インターフェイス | MDN
参考「【JavaScript】各ブラウザでダウンロード処理を実装する(Chrome, Firefox, IE, MS Edge, Safari) | Black Everyday Company」(※情報が古く現在と異なる部分があります)

IE
const blob = new Blob(['test text\n'], {type: 'text/plain'});
const name = 'test.txt';

window.navigator.msSaveBlob(blob, name);

(URL ではないですが) Blob と IE 独自の window.navigator.msSaveBlob() を使うと保存できます。

1.3. その他の方法

ファイル名を指定しなくて良いなら、window.location.hrefwindow.open(url, '_blank') によるファイル保存も考えられそうです。

参考「JavaScriptで動的に作成したテキストファイルをダウンロード - Qiita
参考「【JavaScript】各ブラウザでダウンロード処理を実装する(Chrome, Firefox, IE, MS Edge, Safari) | Black Everyday Company」(※再掲)

2. ファイル開く

保存と同様に、HTML 要素を作ってクリックイベントを発火してファイルダイアログを表示します。

Firefox, Chrome, Edge ※
const showOpenFileDialog = () => {
    return new Promise(resolve => {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.txt, text/plain';
        input.onchange = event => { resolve(event.target.files[0]); };
        input.click();
    });
};

const readAsText = file => {
    return new Promise(resolve => {
        const reader = new FileReader();
        reader.readAsText(file);
        reader.onload = () => { resolve(reader.result); };
    });
};

(async () => {
    const file = await showOpenFileDialog();
    const content = await readAsText(file);
    // 内容表示
    alert(content);
})();

※ Edge では input.accept によるファイルタイプの指定が反映されません。

(Chrome ではページ表示と同時に実行するとダイアログが表示されないようです…)

参考「Can I use... Support tables for HTML5, CSS3, etc
コメント「Promise使うとこんな感じに書けるように...

IE
const reader = new FileReader();
reader.onload = function() {
    // 内容表示
    alert(reader.result);
};

const input = document.createElement('input');
input.type = 'file';
input.accept = '.txt, text/plain';
input.onchange = function(event) {
    reader.readAsText(event.target.files[0]);
};
input.click();

IE はアロー関数や Promise に対応していないので、昔ながらの書き方にします。

(msLoadBlob ってありそうでないんですね…)

3. おまけ

もともとこの記事に書いてあったコードや説明を残しておきます。

3.1. jQuery を使ったコード

ファイル保存 (Data URI)
$('<a>', {
    href: 'data:text/plain,' + encodeURIComponent('test text'),
    download: 'test.txt'
})[0].click(); // ※ [0].click()
ファイル保存 (Blob URL)
$('<a>', {
    href: window.URL.createObjectURL(new Blob(['test text'], {type : 'text/plain'})), // ※ window.URL
    download: 'test.txt'
})[0].click(); // ※ [0].click()
  • [0].click()
  • window.URL
    • 古い Chrome などでは window.URL = window.URL || window.webkitURL; すると良い

参考「例: オブジェクト URL で画像を表示 - Web アプリケーションからファイルを扱う - Web API インターフェイス | MDN
参考「JavaScriptでファイルダウンロード処理を実現する - Qiita

ファイル開く
var reader = new FileReader();
reader.onload = function(event) {
    console.log(event.target.result);
};

$('<input type="file" accept=".txt, text/plain">').on('change', function(event) {
    reader.readAsText(event.target.files[0]);
})[0].click();

3.2. 画像ファイル読み込み

今回はテキストなので readAsText() メソッドを用いていますが、画像の場合は URL.createObjectURL() してから readAsDataURL() すると良いらしいです。

参考「input[type=file]で投稿画像を即時表示する際のメモ - Qiita

3.3. 好きな DOM 要素を <input type="file"> のように使う

本記事で紹介した方法を使えば、好きな DOM 要素で発生したイベントハンドラ内でダイアログを出せるので、好きな DOM 要素を <input type="file"> のように使うことができます。

本記事では <input type="file"> を JavaScript で作っていますが、あらかじめ HTML 上に記述しておく方法もいくつかあります。

  • <input type="file">display: none; して HTML に記述しておき、他から click() を呼ぶ
  • <input type="file">display: none; して <label> で囲む (<label> のクリックに反応する)
  • <input type="file"> を他の HTML 要素にかぶせ、opacity: 0; する

(「<input type="file"> デザイン 変更」などで検索すると色々見つかります)

3.4. ブラウザ確認バージョン

ブラウザのバージョンによってはよりスマートな書き方ができるかもしれません…。

  • Google Chrome 69
  • Firefox 62
  • Edge 42
  • IE 11