JavaScript
HTML5

JavaScriptで動的に作成したテキストファイルをダウンロード

概要

JavaScriptでブラウザのメモリの内容からテキストファイルを生成してダウンロードさせます。
次の2つの手順に分けます。

  1. URLを作る
  2. ダウンロードする

URLを作る

ダウンロード用のURLを作る方法は2つあります。

data URIs

data URIsでコンテンツタイプとコンテンツを指定したURLスキームを作ります。
例えば:

data:,hello

です。

エスケープ

直接文字列を指定した場合、改行が無視されます。整形したJSONファイルを作る際はencodeURIComponentを使ってエンコードします。
例えば:

`data:,${encodeURIComponent(JSON.stringify(data, null, 2))}`

Blob URLs(HTML5)

BlobオブジェクトURL.createObjectURLを使ってURLを作ります。
例えば:

URL.createObjectURL(new Blob(['hello'], {
  type: "text/plain"
})

まとめ

生成する文字列が短い場合はdata URIsを使いましょう。
長文をurlエンコードすると、サイズが大きくなることがあります。Blob URLsを使いましょう。

ダウンロードする

window.location

location.hrefに上で作成したURLを代入するとダウンロードが開始されます。

data URIs

例えば

location.href = 'data:text/plain;charset=UTF-8,hello'

です。

GoogleChromeでの制約

GoogleChrome(Version 60.0.3112.101)で、window.locationを使ってダウンロードしようとすると以下のエラーが出ます。

Screen Shot 2017-08-25 at 16.04.41.png

Not allowed to navigate top frame to data URL: data:text/plain;charset=UTF-8,hello

これを回避するには

Intent to Deprecate and Remove: Top-frame navigations to data URLs - Google グループ によると

  • non-browser-handled MIME types
  • download属性

のいずれかを使います。

non-browser-handled MIME typesを使う場合は、MIME typeにapplication/octet-streamを指定します。
例えば

location.href = 'data:application/octet-stream, hello'

です。この場合でも

Screen Shot 2017-08-25 at 16.05.54.png

Resource interpreted as Document but transferred with MIME type application/octet-stream: "data:application/octet-stream, hello".

とWarningが表示されます。

download属性を使う方法は後述します。

FirefoxとSafariでの制約

Firefox(54.0.2), Safari(Version 10.1.1 (12603.2.4))では、MIME typeがdefault(text/plan)の場合、ダウンロードされずに遷移します。
MIME typeにapplication/octet-streamを指定すれば、ダウンロードが開始されます。

blob URLs

例えば:

location.href = URL.createObjectURL(new Blob(['hello'], {
  type: "text/plain"
}))

GoogleChromeでの制約

GoogleChrome(Version 60.0.3112.101)では、MIME typeがdefault(text/plan)の場合、ダウンロードされずに遷移します。
MIME typeにapplication/octet-streamを指定すれば、ダウンロードが開始されます。

location.href = URL.createObjectURL(new Blob(['hello'], {
  type: "application/octet-stream"
}))

この場合でも

Screen Shot 2017-08-26 at 10.42.57.png

Resource interpreted as Document but transferred with MIME type application/octet-stream: "blob:null/30e18e5d-120f-41e5-837b-b4f015e94ae0".

とWarningが表示されます。

FirefoxとSafariでの制約

Firefox(54.0.2), Safari(Version 10.1.1 (12603.2.4))では、MIME typeがtext/planの場合、ダウンロードされずに遷移します。
MIME typeにapplication/octet-streamを指定すれば、ダウンロードが開始されます。

aタグ download属性(HTML5)

HTML5ではaタグにdownload属性が追加されました。
download属性を指定すると

  1. ページ遷移がファイルダウンロードに
  2. ダウンロードするファイル名を指定できる

例えば:

<a id="link1" download="hello.txt" href="data:,hello">Data Scheme URL</a>

動的に作成したファイルを設定する場合は、aタグのclickイベントハンドラーでhref属性を書き換えます。
例えば:

document
  .querySelector('a')
  .addEventListener('click', (e) => e.target.href = `data:application/json;charset=UTF-8,${JSON.stringify({massage:"hello"})}`)

Blob Scheme URLを使うこともできます。例えば:

document
  .querySelector('a')
  .addEventListener('click', (e) => e.target.href = URL.createObjectURL(new Blob(['hello'], {
    type: "text/plain"
})))

リンククリック時のブラウザの遷移動作はイベントハンドラーの後に実行されます。

まとめ

  • マルチブラウザ対応
  • ファイル名を指定したい

ならば、download属性を使いましょう。

サンプルコード

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>text file download</title>
</head>

<body>
    <article>
        <h1>location.href</h1>
        <button id="button1">data URIs(With default MIME type)</button>
        <button id="button2">data URIs(With MIME type application/octet-stream)</button>
        <button id="button3">Blob URLs(text/plain)</button>
        <button id="button4">Blob URLs(application/octet-stream)</button>
    </article>
    <article>
        <h1>a tag download attribute</h1>
        <a id="link1" download="hello.txt" href="data:,hello">data URIs(text)</a>
        <a id="link2" download="hello.json" href="data:,hello">data URIs(JSON)</a>
        <a id="link3" download="hello.txt" href="#">Blob URLs(text)</a>
        <a id="link4" download="hello.json" href="#">Blob URLs(JSON)</a>
    </article>
    <script type="text/javascript">
        const data = {
            massage: "hello",
            detail: {
                count: 1,
                isError: false,
                content: 'ワールド'
            }
        }

        document.querySelector('#button1').addEventListener('click', () => location.href = 'data:,hello')
        document.querySelector('#button2').addEventListener('click', () => location.href = 'data:application/octet-stream, hello')
        document.querySelector('#button3').addEventListener('click', () => location.href = URL.createObjectURL(new Blob(['hello'], {
            type: "text/plain"
        })))
        document.querySelector('#button4').addEventListener('click', () => location.href = URL.createObjectURL(new Blob(['hello'], {
            type: "application/octet-stream"
        })))
        document.querySelector('#link2').addEventListener('click', (e) => {
            e.target.href = `data:,${encodeURIComponent(JSON.stringify(data, null, 2))}`
        })
        document.querySelector('#link3').addEventListener('click', (e) => e.target.href = URL.createObjectURL(new Blob(['hello'], {
            type: "text/plain"
        })))
        document.querySelector('#link4').addEventListener('click', (e) => e.target.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], {
            type: "text/plain"
        })))
    </script>
</body>

</html>

参考