にしし ふぁくとりー(西村文宏 個人サイト)

"JavaScript Tips Factory" : Presented by Nishishi. Since 1997.

ファイル送信フォームをアイコンに変えた上で、送信前に画像をプレビュー表示する方法 [入力フォーム,情報取得,装飾・内容変更]

ウェブ上の入力フォームを使って画像などのファイルをアップロードさせたい際には、input要素で作るファイル選択ボックスを使います。しかし、このファイル選択フォームはCSSを使って装飾しにくいので、アイコン化(ボタン化)して掲載すると便利です。しかし、それだと「今いくつファイルを選択しているのか?」が分かりにくくなるデメリットがあるため、JavaScriptを併用して「選択中の画像プレビュー」を並べて表示できるようにすると大変便利になります。画像をアップロードさせることなく、ウェブ上にプレビュー表示する方法を解説。

ファイル送信フォームをアイコン表示に変えた上で、送信前に画像をプレビューしたい

ウェブ上の入力フォームを使って画像などのファイルをアップロードさせたい際には、input要素で作るファイル選択ボックスを使います。しかし、このファイル選択フォームはCSSを使って装飾しにくいので、アイコン化(ボタン化)して掲載すると便利です。しかし、それだと「今いくつファイルを選択しているのか?」が分かりにくくなるデメリットがあるため、JavaScriptを併用して「選択中の画像プレビュー」を並べて表示できるようにすると大変便利になります。

独自にボタン化したファイル選択フォームでファイルを選択した状態の表示例

input要素で作るファイル選択ボックスをアイコン化(ボタン化)する方法だけについては、過去に当サイトのCSS Tipsコーナー内で解説しました。記事「ファイル送信フォームのUIをアイコン(ボタン)表示に変える方法」をご参照下さい。HTMLでlabel要素を活用し、CSSで装飾するだけで実現できます。とても簡単です。

その結果、ファイル選択フォームを以下のようなボタン形状に装飾できますが、それだけだと「いくつのファイルが選択されているのか?」も表示できませんし、選択中ファイルのプレビュー表示もできない不便さがあります。

そこで今回は、さらにJavaScriptを加えて、選択中の画像を(アップロードすることなく)プレビュー表示する方法を解説します。アップロード前にプレビュー表示できるので、動作は軽快ですし、無駄な通信も発生しないため便利です。

ファイル送信前に選択された画像をプレビューさせるためのHTMLソース

まずは、HTMLソースを記述しましょう。
下記のHTMLソースには、以下のような要素が含まれています。

  • ファイル選択フォームをボタン化するための部分 (label要素とそこに含まれるimg要素やspan要素)
  • 選択中の画像をプレビュー表示するための描画領域 (span要素)
  • ファイルの選択状態をリセットするためのボタン (input要素)

HTMLソース

<form action="https://example.com/post/form/" method="post" enctype="multipart/form-data">
   <p>
      <label>
         <span class="filelabel" title="ファイルを選択">
            <img src="camera-orange-rev.png" width="32" height="26" alt="+画像">
            <span id="selectednum">選択</span>
         </span>
         <input type="file" name="datafile" id="filesend" multiple accept=".jpg,.gif,.png,image/gif,image/jpeg,image/png">
      </label>
      <span id="previewbox"></span>
      <input type="reset" value="Reset" onclick="resetPreview();"><!-- ※これはリセットボタン(省略可) -->
   </p>
</form>

ファイル選択フォームをボタン化するための部分については、CSS Tips側の記事「ファイル送信フォームのUIをアイコン(ボタン)表示に変える方法」で解説済みなのでここでは省略します。

※上記のソースでは、11行目にリセットボタンを配置しています。これは省略しても動作に問題はありません。後述するJavaScriptで作るリセット用関数resetPreviewを単独で実行しています。

HTMLソースの段階では、単にプレビューを表示するための空間を用意しているだけです。
今の段階では、下記のように普通のファイル選択フォームとして表示されるだけです。

ファイル選択フォームを表示するHTMLソースを書いただけの状態

ここから、CSSで装飾を加えて、JavaScriptで動作を加えます。

ファイル送信前に選択された画像をプレビューさせるためのCSSソース

次に、先ほど書いた「ファイル選択フォームとプレビュー用領域を表示するHTML」に対して、CSSで装飾を追加しましょう。
例えば、下記のようにCSSソースを記述します。

CSSソース

.filelabel {
   background-color: orange;  /* 背景色 */
   color: white;              /* 文字色 */
   border: 2px solid orange;  /* 枠線 */
   border-radius: 3em;        /* 角丸 */
   padding: 12px 9px;         /* 内側の余白 */
   display: inline-block;     /* インラインブロック化 */
}
.filelabel img {
   vertical-align: bottom;    /* 画像の垂直方向の配置 */
}
.filelabel:hover {
   opacity: 0.5;              /* 半透明 */
   border: 2px solid red;     /* 枠線(赤色) */
   cursor: pointer;           /* マウス形状(ポインタ) */
}
#filesend {
   display: none;  /* 本来のファイル選択フォームは非表示に */
}
#previewbox {
   display: inline-block;     /* プレビュー領域をボタンと横並びに配置する */
   vertical-align: middle;    /* プレビュー画像の垂直方向の配置 */
}

上記のCSSソースのうち、1行目~19行目までは、ファイル選択フォームをボタン化するための部分です。それらはCSS Tips側の記事「ファイル送信フォームのUIをアイコン(ボタン)表示に変える方法」で解説済みなのでここでは省略します。

ここで加えているのは、プレビュー領域を装飾する4行だけです。
特別なことは何もしていません。単に、

  • プレビュー領域が「ファイル選択フォーム(ボタン)」と横並びになるよう、インラインブロック化していることと
  • プレビュー画像が(垂直方向の)中央寄せで表示されるようにしている

というだけです。
この段階では、下記のようにファイル選択フォームが独自のボタン形状で見えるようになります。

画像説明

クリックすればもちろんファイル選択機能が働きますが、本来のファイル選択フォームを非表示にしているため、「何のファイルを選択中なのか」(または何も選択されていない状態なのか)が見えません。そこで、次にJavaScriptで画像プレビュー機能を加えます。

ファイル送信前に選択された画像をプレビューさせるためのJavaScriptソース

次に、今回の本題である、選択された画像を送信前にプレビューするためのJavaScriptソースを書きましょう。
まずは、記述する必要のあるJavaScriptソースの全体を掲載しておきます。

JavaScriptソース

// ▼①ファイル選択フォームの更新イベントに処理を追加
document.getElementById("filesend").addEventListener('change', function(e) {
   var files = e.target.files;
   previewUserFiles(files);
});
// ▼②選択画像をプレビュー
function previewUserFiles(files) {
   // 一旦リセットする
   resetPreview();
   // 選択中のファイル1つ1つを対象に処理する
   for (var i = 0; i < files.length; i++) {
      // i番目のファイル情報を得る
      var file = files[i];
      // 選択中のファイルが画像かどうかを判断
      if( file.type.indexOf("image") < 0 ) {
         /* 画像以外なら無視 */
         continue;
      }
      // ファイル選択ボタンのラベルに選択個数を表示
      document.getElementById("selectednum").innerHTML = (i+1) + "個選択中";
      // 画像プレビュー用のimg要素を動的に生成する
      var img = document.createElement("img");
      img.classList.add("previewImage");
      img.file = file;
      img.height = 100;   // プレビュー画像の高さ
      // 生成したimg要素を、プレビュー領域の要素に追加する
      document.getElementById('previewbox').appendChild(img);
      // 画像をFileReaderで非同期に読み込み、先のimg要素に紐付けする
      var reader = new FileReader();
      reader.onload = (function(tImg) { return function(e) { tImg.src = e.target.result; }; })(img);
      reader.readAsDataURL(file);
   }
}
// ▼③プレビュー領域をリセット
function resetPreview() {
   // プレビュー領域に含まれる要素のすべての子要素を削除する
   var element = document.getElementById("previewbox");
   while (element.firstChild) {
      element.removeChild(element.firstChild);
   }
   // ファイル選択ボタンのラベルをデフォルト状態に戻す
   document.getElementById("selectednum").innerHTML = "選択";
}

※上記のソースは、MDNサイト内の「Web アプリケーションからファイルを扱う」ページに含まれる「例: ユーザが選択した画像のサムネイルを表示」に掲載されているソースをそのままコピーして使っている箇所が多くあります。

説明のしやすさから、①→③→②の順で以下に説明します。

①ファイル選択フォームの更新イベントに処理を追加

まずは、1行目~5行目です。

JavaScriptソース

// ▼①ファイル選択フォームの更新イベントに処理を追加
document.getElementById("filesend").addEventListener('change', function(e) {
   var files = e.target.files;
   previewUserFiles(files);
});

▼2行目:ファイル選択フォームの更新イベントに処理を追加

ファイル選択フォームHTMLソースでは、id属性を使って「filesend」というid名を割り振りました。
なので、getElementByIdメソッドを使って document.getElementById("filesend") のように書けば、ファイル選択フォームに対して何らかの操作ができます。
ここではファイルの選択状態が変化する度にプレビュー処理を実行させたいわけですから、onchangeイベントを利用すれば良いでしょう。
addEventListenerメソッドを使って 対象.addEventListener('change', function(e) { ~処理~ }); などと書けば、対象のonchangeイベント発生時に独自の処理を実行できます。
これが2行目に書いてあることです。

▼3行目:選択中のファイル群を得る

2行目でaddEventListenerメソッドの第2引数に無名関数を記述していますが、 function(e) { ~ } と書くことで、eにはイベントオブジェクトが渡されます。
それによって、3行目にあるように var files = e.target.files; で、選択されたファイル情報の一覧を変数filesに格納できます。

▼4行目:望みの処理を実行する関数に渡す

最後に、得られた方法(=files変数の中身)を、メイン処理を実行する関数previewUserFilesに渡しています。
関数previewUserFilesは、7行目~33行目に書いています。解説は後述します。

③プレビュー領域をリセットする関数を作る

次に、34行目以降を先に解説しておきます。ここでは、プレビュー表示を取り消して、ファイル選択ボタンのラベルを初期化しています。

JavaScriptソース

// ▼③プレビュー領域をリセット
function resetPreview() {
   // プレビュー領域に含まれる要素のすべての子要素を削除する
   var element = document.getElementById("previewbox");
   while (element.firstChild) {
      element.removeChild(element.firstChild);
   }
   // ファイル選択ボタンのラベルをデフォルト状態に戻す
   document.getElementById("selectednum").innerHTML = "選択";
}

プレビュー用として動的に生成するimg要素は、ユーザがファイルの選択を解除しても勝手には消えません。なので、それらを消す処理を書く必要があります。それがここで作っているresetPreview関数です。
プレビュー領域(id属性値がpreviewboxの要素)の中に含まれる子要素をひたすら削除します。

▼37行目:プレビュー用領域を取得

画像プレビュー用に設けた領域(span要素)には、id属性を使って「previewbox」というid名を割り振りました。
なので、getElementByIdメソッドを使って document.getElementById("previewbox") のように書けば、その要素に対して何らかの操作ができます。(ここでは、変数elementに格納しています。)

▼38~40行目:子要素があるだけループ

プレビュー領域のspan要素(=変数element)内にある先頭の子要素は element.firstChild で得られます。
その子要素を削除するには、removeChildメソッドを使って element.removeChild(element.firstChild); のように書けます。
while文でループすることで、存在する子要素を全て削除することができます。

▼41行目:ファイル選択ボタンのラベルを戻す

ファイル選択ボタンのラベルには、選択中のファイル数を表示する仕様にしていますので、無選択状態を示す初期値として「選択」という文字列を表示させます。
フォーム選択ボタンのラベルテキストには、span要素にid属性値「selectednum」を指定していましたので、getElementByIdメソッドとinnerHTMLプロパティを使って書き換えています。

②ユーザが選択した画像をその場でプレビューする処理を記述する

最後に、7行目~33行目です。
ここがメインの処理ですね。
関数previewUserFilesは、大きく分けて以下のような構成になっています。

  • 9行目:一旦、プレビュー表示やボタン表示をリセットする。
  • 11~32行目:選択中のファイル1つ1つをループで処理する。
    • 13~18行目:ファイル情報を得て、画像ファイルかどうかを判断する。(画像でなければ無視して次のループへ進む)
    • 20行目:ファイル選択ボタンのラベルに、選択中の個数を表示する。
    • 22~27行目:画像プレビュー用のimg要素を動的に生成して表示する。
    • 29~31行目画像を非同期に読み込んで、先のimg要素に表示する。
▼9行目:一旦、プレビュー表示やボタン表示をリセットする。

これは単に、先ほど作成した関数resetPreviewを実行しているだけです。

JavaScriptソース(抜粋)

   // 一旦リセットする
   resetPreview();

▼11~32行目:選択中のファイル1つ1つをループで処理する。

選択中のファイル情報は変数filesに格納されています。
選択されているファイルの総数は、files.length で得られます。
したがって、for文を使って for(var i = 0; i < files.length; i++) { ~処理内容~ } などと書くことで、ファイルの個数分だけループできます。選択中のファイルが1つもない場合は、ループ内部の処理は1度も実行されません。

▼13~18行目:ファイル情報を得て、画像ファイルかどうかを判断する。

JavaScriptソース(抜粋)

      // i番目のファイル情報を得る
      var file = files[i];
      // 選択中のファイルが画像かどうかを判断
      if( file.type.indexOf("image") < 0 ) {
         /* 画像以外なら無視 */
         continue;
      }

files[i]でi番目のファイル情報を得て、変数fileに格納しています。
ここでは画像ファイルだけを対象にしてプレビューさせるため、ファイルタイプを調べて「image」という文字列が含まれているかどうかを確認しています。
もし含まれていない場合は、「画像ではない」と判断して次のループへ進みます。

▼20行目:ファイル選択ボタンのラベルに、選択中の個数を表示する。

JavaScriptソース(抜粋)

      // ファイル選択ボタンのラベルに選択個数を表示
      document.getElementById("selectednum").innerHTML = (i+1) + "個選択中";

ファイル選択ボタンのラベルには、id名「selectednum」が付加されています。
getElementByIdメソッドとinnerHTMLプロパティを使って、ラベルの表示に選択中のファイル個数を表示させています。

▼22~27行目:画像プレビュー用のimg要素を動的に生成して表示する。

JavaScriptソース(抜粋)

      // 画像プレビュー用のimg要素を動的に生成する
      var img = document.createElement("img");
      img.classList.add("previewImage");
      img.file = file;
      img.height = 100;   // プレビュー画像の高さ
      // 生成したimg要素を、プレビュー領域の要素に追加する
      document.getElementById('previewbox').appendChild(img);

プレビュー画像を表示するためには、画像1つ1つに対して、表示用のimg要素を用意しなければなりません。
ファイルがいくつ選択されるのかが事前に分からない以上、img要素は動的に生成するしかありません。
そこで、createElementメソッドを使ってimg要素を生成し、必要な属性値を付加しています。

動的に生成されたプレビュー表示用のimg要素をCSSで装飾したい場合に備えて、23行目ではimg.classList.add("previewImage");のようにして、class名「previewImage」を加えています。特にCSSで装飾しないなら、この記述は不要です。
また、プレビューの大きさを制限するため、25行目ではheight属性値に高さを入れています。(CSSで大きさを制限するなら、この行は不要ですが。)

最後に、プレビュー表示領域用の要素(id=”previewbox”)に対して、appendChildメソッドを使って、今作成したimg要素を追加しています。

▼29~31行目画像を非同期に読み込んで、先のimg要素に表示する。

JavaScriptソース(抜粋)

      // 画像をFileReaderで非同期に読み込み、先のimg要素に紐付けする
      var reader = new FileReader();
      reader.onload = (function(tImg) { return function(e) { tImg.src = e.target.result; }; })(img);
      reader.readAsDataURL(file);

FileReaderオブジェクトを使うと、閲覧者のローカル環境にあるファイルを非同期で読み込むことができます。この方法なら、ユーザにファイルをアップロードさせることなく、ブラウザ上に画像を表示できます。

ここでは、29行目で新しいFileReaderオブジェクトを生成し、
30行目で、onloadイベントに処理(後述)を記述し、
31行目で、readAsDataURLメソッドを呼んで、ローカルにあるファイルを非同期で読み込みます。

画像ファイルの読み込みが完了すると、onloadイベントが発生するので30行目に書いた処理が実行されます。
読み込まれた画像は、Base64でエンコードされたdata:URLの形で得られますので、ここではそのデータを、img要素のsrc属性値に指定しています。
これによって、ローカルにファル画像ファイルのプレビューが実現します。

ファイル送信フォームをアイコン表示に変えた上で、送信前に画像をプレビューする表示&動作例

上記のHTML+CSS+JavaScriptソースを実際に表示させると、以下のように見えます。

※画像以外のファイルが選択された場合には、プレビューは表示されませんし選択個数にもカウントされませんが、「ファイルの選択状態」はそのままです。もしユーザがそのままの状態で送信ボタンを押せば、画像以外のファイルもアップロードされる点には注意してください。最終的には、ファイルを受け取るサーバ側でデータをより分ける必要があります。

上記の『独自ボタン型ファイル選択フォーム』の動作サンプルは、以下のように動作するはずです。

  • ファイル選択ボタンの上にマウスポインタが載ったら、枠線が付加されてボタンは薄くなる。
  • ファイル選択ボタンがクリックされたら、(ブラウザの)ファイル選択フォームが表示される。
  • ユーザが画像ファイルを選択したら、選択個数がボタン表面に表示された上で、ボタンの横にプレビュー画像が表示される。
  • リセットボタンを押すと、すべてが初期状態に戻る。

以上、ファイル送信フォームのUIをアイコン(ボタン)表示に変えた上で、選択中ファイルを送信前にプレビュー表示する方法でした。
ぜひ、使ってみて下さい。

()

JavaScript TIPSふぁくとりーの主要なカテゴリ

下記のカテゴリに区分して、JavaScriptに関するTIPSを公開しています。カッコ内の数字は、該当する記事の件数です。

著者紹介


にしし(西村文宏)

にししでございます。本書いたり記事書いたりしてます。あと萌えたり。著書5冊発売中です(Web製作系4冊+小説1冊)。著書や記事は「西村文宏」名義。記事は主にAll Aboutで連載。本の最新刊は2011年3月に発売されたライトノベルでございますよ。

Twitter:にしし/西村文宏
にしし/西村文宏 on facebook にしし/西村文宏 on mixi にしし/西村文宏 on Google+ フォローはお気軽に!

にしし(西村文宏)連絡先
☕ コーヒーをおごる

著書一覧と詳細

関連するかもしれない情報など

▼このページに関連しそうな記事が約8本くらい自動表示されています。(たぶん)

--- 当サイト内を検索 ---