file , reader API を使ってファイルを読み込む

記事の作成日 : 2020/03/07

前から少し、ファイルの読み込みってどうやるんだろうって、疑問に思っていたので、
それを解決します。(あと、ファイル操作をやってみたかったのもある。)

全く関係のない話
デバッグをする時に、いつもFirefoxを使っているのですが、
ふと、Operaの開発者ツールを見たときに、Firefoxには無かった様々な
デバッグオプションがあったことに驚きました。
CSSのインライン描画機能、各割り当てメモリのグラフ、レンダリングからFPSの表示!
超便利じゃないか、と思いました。

テストをしていて挙動がおかしかったら、IE,firefox,Opera とかでもテストしています。
適材適所、っていうやつですね。(' ')

先にサンプル

サンプルの内容

  1. ファイル選択されたら、ファイルを読み込むための、関数が実行される
  2. オブジェクトのファイル情報を取得する
  3. オブジェクトから、ファイルの内容を読み込む命令を実行させる
  4. その命令が、(読み込み中、読み込んだ、エラー)になった時の処理を作る
  5. 終わり

サンプルのコード

サンプルのコードが、少々長いので格納しています。
最初に書いたコード(こっちのコードをサンプルに使っています。)
    <input type="file" id="data" style="width: 100%;">

    <div id="output"></div>
    <progress style="width:100%;height: 2em;" value="" max="" id="loaded"></progress>
    <div class="srcode" id="console"></div>

    <div id="tag"></div>
    <!-- <div id="tag"></div> -->

    <script>

        document.getElementById("data").addEventListener("change", ReadFile2, false);

        function ReadFile2(e) {
            var f = e.target.files[0]; // files の0番目を扱う

            
            var Fname = f.name;
            var Flast = f.lastModified;
            var FlastString = f.lastModifiedDate;
            var Fsize = f.size;
            var Ftype = f.type;
            // 表を作成するところ
            var out = ""; // 空文字列
            out += "<table style='width:100%;' border='1'>"
            out += "<tr><th><br>プロパティー<br></th><th><br>値<br></th></tr>"
            out += "<tr><th>.name (ファイル名)</th><td>" + escape(Fname) + "</td><tr>"
            out += "<tr><th>.lastModified (ファイルの最終更新日)</th><td>" + Flast + "</td><tr>"
            out += "<tr><th>.lastModifiedDate (ファイルの最終更新日 テキスト)</th><td>" + Flast + "</td><tr>"
            out += "<tr><th>.size (ファイルサイズ)</th><td>" + Fsize + "</td><tr>"
            out += "<tr><th>.type (MIMEタイプ)</th><td>" + Ftype + "</td><tr>"
            out += "</table>"

            document.getElementById("output").innerHTML = out; // それを出力

            // 大量のメモリ使用 の確認 おそらく、ファイルの容量のその8~10倍のメモリを食います。[base64 data: 方式]
            if (10000000 <= Fsize) {
                if (confirm("大量のメモリを使用します。\n\n(デバイスによって異なりますが、元のファイルの容量の,その8倍以上のメモリを必要とします。)\n使用メモリ(ピーク予測):" + Math.round((Fsize / 1024 / 1000) * 8) + " MB以上\n\n続行してよろしいですか?。") != 1) {
                    return; // OK 以外なら続きを実行しない。
                }
            }

            // ファイルを読み込む処理
            var r = new FileReader(); // File Reader API を使う

            //目に見える部分 のやつの管理
            var elm_loaded = document.getElementById("loaded");
            var elm_console = document.getElementById("console");
            var elm_tag = document.getElementById("tag");

            // 初期値 の設定
            elm_loaded.setAttribute("max", Fsize);
            elm_console.textContent = "0 KB"

            // 読み込み開始

            r.readAsDataURL(f); // コンソールに出力するとBase64形式。resultで出力 



            // 読み込み中 の処理
            r.addEventListener("progress", function(e) {
                elm_console.textContent = Math.round(e.loaded / 1024) + " KB";
                elm_loaded.setAttribute("value", e.loaded);
            });

            // 読み込んだ 時の処理
            r.addEventListener("load", function() {
                elm_console.textContent = "Loaded! 読み込めました!: " + Math.round(Fsize / 1024) + " KB";
                elm_loaded.setAttribute("value", Fsize);


                elm_tag.innerHTML = '';


                if (Ftype.indexOf("audio") != -1) {
                    elm_tag.innerHTML = '<audio controls style="width:100%" src="' + r.result + '"><code>Audio</code>に対応していません。</audio>'; // どっちが早いんでしょうか…
                } else if (Ftype.indexOf("image") != -1) {
                    elm_tag.innerHTML = '<img style="width:100%;" src="' + r.result + '">';
                } else if (Ftype.indexOf("video") != -1) {
                    elm_tag.innerHTML = '<video controls style="width:100%;" src="' + r.result + '"><code>Video</code>に対応していません。</video>';
                } else {
                    elm_tag.innerHTML = '<div style="width:100%;">' + escape(r.result) + '"</div>';
                }
            });

            // 読み込みに失敗した 時の処理
            r.addEventListener("error", function(e) {
                elm_console.textContent = "何らかのエラーが発生しました";
            });


        }

        function escape(s) {

            return s.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
        }
    </script>
書き換えたコード
     <style>
            .none {
                display: none;
                visibility: hidden;
            }
            
            .visible {
                display: contents;
                visibility: visible;
            }
        </style>

        <input type="file" id="data" style="width: 100%;">
        <span style="background-color: khaki;">サーバーにアップロードされることはありませんが、安全のため、個人情報を含むファイルを選択しないようにしてください。</span>

        <div id="output" style="width: 100%;"></div>
        <progress style="width:100%;height: 2em;" value="" max="" id="loaded"></progress>
        <div class="srcode" id="console" style="text-align: center; color: white;"></div>

        <audio controls id="audio" class="none"><code>Audio</code>に対応していません。</audio>
        <img controls id="image" class="none">
        <video controls id="video" class="none"><code>Video</code>に対応していません。</video>
        <div id="text" class="none"></div>
        <!-- <div id="tag"></div> -->

        <script>

            document.getElementById("data").addEventListener("change", ReadFile, false);

            function ReadFile(e) {
                var f = e.target.files[0]; // files の0番目を扱う

                
                var Fname = f.name;
                var Flast = f.lastModified;
                var FlastString = f.lastModifiedDate;
                var Fsize = f.size;
                var Ftype = f.type;
                // 表を作成するところ
                var out = ""; // 空文字列
                out += "<table style='width:100%;' border='1'>"
                out += "<tr><th><br>プロパティー<br></th><th><br>値<br></th></tr>"
                out += "<tr><th>.name (ファイル名)</th><td>" + escape(Fname) + "</td><tr>"
                out += "<tr><th>.lastModified (ファイルの最終更新日)</th><td>" + Flast + "</td><tr>"
                out += "<tr><th>.lastModifiedDate (ファイルの最終更新日 テキスト)</th><td>" + Flast + "</td><tr>"
                out += "<tr><th>.size (ファイルサイズ)</th><td>" + Fsize + "</td><tr>"
                out += "<tr><th>.type (MIMEタイプ)</th><td>" + Ftype + "</td><tr>"
                out += "</table>"

                document.getElementById("output").innerHTML = out; // それを出力

                // 大量のメモリ使用 の確認 おそらく、ファイルの容量のその8~10倍のメモリを食います。[base64 data: 方式]
                if (10000000 <= Fsize) {
                    if (confirm("大量のメモリを使用します。\n\n(デバイスによって異なりますが、元のファイルの容量の,その8倍以上のメモリを必要とします。)\n使用メモリ(ピーク予測):" + Math.round((Fsize / 1024 / 1000) * 8) + " MB以上\n\n続行してよろしいですか?。") != 1) {
                        return; // OK 以外なら続きを実行しない。
                    }
                }

                // ファイルを読み込む処理
                var r = new FileReader(); // File Reader API を使う

                //目に見える部分 のやつの管理
                var elm_loaded = document.getElementById("loaded");
                var elm_console = document.getElementById("console");
                var elm_text = document.getElementById("text");
                var elm_audio = document.getElementById("audio");
                var elm_image = document.getElementById("image");
                var elm_video = document.getElementById("video");

                // 初期値 の設定
                elm_loaded.setAttribute("max", Fsize);
                elm_console.textContent = "0 KB"

                // 読み込み開始

                r.readAsDataURL(f); // コンソールに出力するとBase64形式。resultで出力 



                // 読み込み中 の処理
                r.addEventListener("progress", function(e) {
                    elm_console.textContent = Math.round(e.loaded / 1024) + " KB";
                    elm_loaded.setAttribute("value", e.loaded);
                });

                // 読み込んだ 時の処理
                r.addEventListener("load", function() {
                    elm_console.textContent = "Loaded! 読み込めました!: " + Math.round(Fsize / 1024) + " KB";
                    elm_loaded.setAttribute("value", Fsize);

                    elm_audio.setAttribute("class", "none");
                    elm_image.setAttribute("class", "none");
                    elm_video.setAttribute("class", "none");
                    elm_text.setAttribute("class", "none");
                    elm_audio.setAttribute("src", "");
                    elm_image.setAttribute("src", "");
                    elm_video.setAttribute("src", "");
                    elm_text.innerHTML = '';


                    if (Ftype.indexOf("audio") != -1) {
                        //elm_tag.innerHTML = '<audio controls src="' + r.result + '"><code>Audio</code>に対応していません。</audio>'; // どっちが早いんでしょうか…
                        elm_audio.setAttribute("class", "visivle");
                        elm_audio.setAttribute("src", r.result);
                    } else if (Ftype.indexOf("image") != -1) {
                        //elm_tag.innerHTML = '<img style="width:50%;" src="' + r.result + '">';
                        elm_image.setAttribute("class", "visivle");
                        elm_image.setAttribute("src", r.result);
                    } else if (Ftype.indexOf("video") != -1) {
                        //elm_tag.innerHTML = '<video controls style="width:50%;" src="' + r.result + '"><code>Video</code>に対応していません。</video>';
                        elm_video.setAttribute("class", "visivle");
                        elm_video.setAttribute("src", r.result);
                    } else {
                        elm_text.setAttribute("class", "visible");
                        elm_text.innerHTML = '<div style="width:50%;">' + escape(r.result) + '"</div>';
                    }
                });

                // 読み込みに失敗した 時の処理
                r.addEventListener("error", function(e) {
                    elm_console.textContent = "何らかのエラーが発生しました";
                });


            }

            function escape(s) {

                return s.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
            }
     </script>

サンプルを解読していく

HTML > ファイル選択ボタン

<input type="file" id="data">
typefile にすると、ファイルの選択ができるようになります。
id は今回は,data とします。
動作テストに、console.log を使用していきます。
Alert などでは、詳しく中身を見ることができないかもしれません。

Javascript > ファイルが選択されたら関数が実行される命令

document.getElementById("data").addEventListener("change", ReadFile, false);
document.getElementById("HTMLで指定したid").addEventListener(イベント, ハンドラ(実行される関数など), false);
ファイルが選択されたかを判別するには、 addEventListenerを使用します。
addEventListenerにとっては、物が変わる ので、イベントには、changeが使われます。
addEventListener - MDN web docs

id: datachange が成立したら、後で作成する関数:ReadFile を、実行します。

Javascript > ファイルが選択されたら実行される関数:ReadFile(e)

これが今回のメインの関数です。ファイルが選択された時の処理を書いていきます。

function ReadFile(e) {
    var f = e.target.files[0]; // files の1番目を扱う
addEventListener から関数を実行した場合、引数 func(引数) を関数のほうに作成しておけば、
そのイベントに見合った値を取得することができます。

今回の場合、ファイルが選択されたら実行される ことになっているので、
そのファイルのオブジェクトを取得することが出来ます。

e.target(e は引数から)で、準備みたいなもので、そのあとの.filesで、ファイルのオブジェクトを取得できます。
そして1番目のオブジェクトを参照するので、files の後に [0]を付け足します。

console.log を使って、f の中身を見てみます

File {name: "orig2.png", lastModified: 1582517238284, lastModifiedDate: Mon Feb 24 2020 13:07:18 GMT+0900 (日本標準時), webkitRelativePath: "", size: 3627942, …}
        name: "orig2.png"
        lastModified: 1582517238284
        lastModifiedDate: Mon Feb 24 2020 13:07:18 GMT+0900 (日本標準時) {}
        webkitRelativePath: ""
        size: 3627942
        type: "image/png"

Javascript > ファイルオブジェクトから、情報を取得する

    var Fname = f.name; // ファイル名
    var Flast = f.lastModified; // ファイルの最終更新日
    var Fsize = f.size; // ファイルサイズ
    var Ftype = f.type; // MIME タイプ

name

ファイル名を取得することが出来ます。

lastModified

ファイルの最終更新日を取得することが出来ます。

lastModifiedDate

ファイルの最終更新日を取得することが出来ます。👎非推奨

size

ファイルの容量を取得することが出来ます。
 単位は、バイト[B] です。

type

ファイルの種類(MIME タイプ)を知ることが出来ます。
video/mp4audio/mp3image/png 、などです。

変数

それぞれの変数に情報を入れています。

Javascript > ファイル自体の内容を読み込む下準備

// ファイルを読み込む処理の下準備
        var r = new FileReader(); // File Reader API を使う
FileReader API を使って、オブジェクトを作成します。

(ふつうは、r ではなく、 reader と名づける方が分かりやすくていいらしいのですが、名前が長すぎても嫌ですし、後でたくさん使うでしょうし。考えた結果、r と名づけました。)
おっと、言い訳が長引きました。

Javascript > ファイルを読み込もう

// 読み込み開始
        r.readAsDataURL(f); // Base64形式。r.result で出力 
readAsDataURL(base64 形式) を使い、ファイルを読み込みます。
注意!!
readAsDataURLなどは、
読み込み開始、読み込み中、読み込み完了などの過程があって、
すぐにファイルの内容を読み取れるわけではないようです!。

またまた登場。addEventListener! 便利!

 // 読み込み開始
        r.readAsDataURL(f); // Base64形式。r.resultで出力 

        // 読み込み開始 の処理
        r.addEventListener("loadstart",function(){
            console.log("読み込み開始")
        });

        // 読み込み中 の処理
        r.addEventListener("progress", function(e) {
            console.log( e.loaded ); // バイト単位で、表示する。 .loaded
        });

        // 読み込んだ 時の処理
        r.addEventListener("load", function() {
            console.log( "読み込み完了!" );
        });

        // 読み込みに失敗した 時の処理
        r.addEventListener("error", function(e) {
            console.log("何らかのエラーが発生しました…");
        });
addEventListenerで、読み込み開始、読み込み中、読み込み完了、読み込みエラー、が発生したときに、関数や、アラー関数が実行されます。

面白いことに!: progress イベントは、読み込んでいる最中に、1回だけではなく、読み込みが完了するまで、定期的に関数またはアラー関数などが実行されるみたいなんですよね!

これをうまく使えば、<progress>と<script>でそれっぽいロードが表示できるんです!
このプログレスバーのコード
<progress id="code_0" value="0" max="20" style="width: 100%;"></progress>
            <script>
                var progress_0 = document.getElementById('code_0');
                setInterval(() => {
                    progress_0.value += Math.random();
                    if (progress_0.value >= 20) {
                        progress_0.value = 0;
                    }
                }, 100);
            </script>

readAsDataURL を含む、それ以外の形式の追加説明

readAsDataURL

readAsDataURL(Blob)
最終的に、出力される形式は、「 BASE64 」
a~z , A~Z , 0~9 で構成される変換されたデータ。
[テキストファイル txt]
        data:text/plain;base64,SW5zdGFs  ...
[音声データ mp3]
        data:audio/mp3;base64,EHwIi2O  ...

readAsText

readAsText(Blob , Encoding:String)
Encodeing(エンコード)によって、テキストが変化します。
最終的に、出力される形式は、「 Text 」
reader.readAsText( f , "UTF-8")
reader.readAsText( f , "UTF-16")

readAsArrayBuffer

readAsArrayBuffer(Blob)
最終的に、出力される形式は、「 Array 」
取得する内容は、
Int8Array() [-255~255] 、 Uint8Array() [0~255]
どれかによって変わります。
var getARR_int8bit = new Int8Array(reader.result);
// [-37,132,83,22,-97, ...]
var getARR_uint8bit = new Uint8Array(reader.result);
// [172,82,12,211,54, ...]
var getARR_uint8bit_clamped = new Uint8ClampedArray(reader.result); // ?

readAsBinaryString

readAsBinaryString(Blob)
読み込んだファイルのデータを、バイナリの文字列として取得できます。
文字列を構成する、文字コードは、 [ 0 ~ 255 ]までです。
最終的に、出力される形式は、「 Text 」
23¾$`Z.ϦSòjaM½C¢FA…9öRn1žãçQ¢Wï}
HTML上に表示させる場合には、エスケープ処理を行うことをお勧めします。


文献、参考など

FileReader - Web API | MDN - Mozilla

<<前 : setTimeoutを使う!
>>次 : Cookieの話