iOS/Androidで、サムネイル表示＋アップロードキャンセル可能な複数ファイルアップロードをFileAPIでやる


HTML5のFile APIを使ってファイルのリアルタイムサムネイル表示なんて、もはや一般的に使われているネタなんだけど、今回はファイル参照ボタンをオリジナルデザインのボタンにしつつ、選択後、個別でキャンセルが出来るようにしてみた。

んだけど、Android Browserで一部、関係ないところで動かなかったりして色々はまってしまった。っていうか、Androidのバージョンによるのかもだけど、そもそもmultiple時に一度に複数画像が選択できないんだけど…。これはどのAndroid機でも同じなのかな。

というわけで、仕様の若干違う2パターン作ってみた。

# パターンA

## 仕様

Android Browserでは動かないので、微妙だけど。

- 一度に選択できる画像は1つだけ
- 複数の画像をアップロード可能
- アップロードする個数を制限可能
- 選択後、サムネイルをクリックでキャンセル

### 動作確認

- iOS7〜8.02（iOS8.0は、FileAPIで不具合ありで動かない）
- Android4.x Chrome

### サンプルコード

```html:a.html
<!doctype html>
<html>
    <head>
    <meta charset="utf8">
      <style type="text/css">
      #upload {
        width:90px;
        height:35px;
        text-align:center;
        line-height:35px;
        border-radius:5px;
        color:#fff;
        background-color:#4385bf;
        margin:10px 0;
        cursor:pointer;
      }
      #results {
        border:1px solid #4385bf;
        background-color:#bbd4ea;
        height:160px;
        padding:5px;
        margin:10px 0;
      }
      #results img {
        border:1px solid #000;
        margin:0 5px 0 0;
      }
      </style>
    <body>
        <form action="index.php" method="post" enctype="multipart/form-data" id="form">
            <button type="submit">submit</button>
        </form>
        <div id="upload">picture</div>
        <div id="results"></div>
        <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
        <script>
        $(function() {
            var makeInput = function() {
              return $('<input type="file" accept="image/jpeg, image/gif, image/png" name="files[]" style="opacity:0;">');
            };

            $('#upload').click(function() {
              var hookInput = makeInput();
              var id = 'i' + parseInt((new Date)/1000);
              hookInput.attr('id', id);
              $('#form').append(hookInput);
              $('#' + id).click();
            });

            function setImage() {
              for (var i = 0; i < this.files.length; i++) {
                var id = $(this).attr('id');
                var file = this.files[i];
                fr = new FileReader();
                fr.onload = function(e) {
                    var img = $('<img>');
                    img.attr('src', e.target.result);
                    img.css('height', '160px');
                    $('#results').append(img);
                    $(img).on('click', {id: id}, removeImage);
                };
                fr.readAsDataURL(file);
                if ($('#results').children().length > 1) {
                  $('#upload').css('background', '#ddd');
                  $('#upload').unbind();
                }
              }
            }
            function removeImage(e) {
              $(this).remove();
              $('#' + e.data.id).remove();
            }

            $(hookInput).on('change', setImage);
        });
        </script>
    </body>
</html>
```

![スクリーンショット 2014-10-11 18.57.33.png](https://qiita-image-store.s3.amazonaws.com/0/2152/f2fbb016-c5a2-0ffd-563a-f0d27d600f06.png "スクリーンショット 2014-10-11 18.57.33.png")

こんな感じで、青いpictureボタンを押すとファイル選択ダイアログが表示される。ファイルを選択するとサムネイルが表示。複数同時には選択できないようにしていて、その代わり、画像をクリックするとアップロードをキャンセルできる。

```
<input type="file" name="files[]" multiple>
```

って、multipleを指定してあげると、ファイル選択時に複数個同時に選択できるんだけど、その後、1個だけ取り消したい！ッていう場合に、操作不可能だったので、個別でinputタグを生成するようにしている。

それからAndroid Browserで動かない件は

```
var a = $('<input type="file">');
$('#form').append(a);
a.click();
```

このclick()が発火しないという…謎の挙動。alert()を途中で挟んであげれば動いたりするので、なんかやり方ありそうなんだけど…。

# パターンB

## 仕様

こっちのが王道っぽい。画像の個別キャンセルができないのが難点か。

- 一度に選択できる画像は複数
- 複数の画像をアップロード可能
- アップロードする個数を制限可能

### 動作確認

- iOS7〜8.02（iOS8.0は、FileAPIで不具合ありで動かない）
- Android4.x Chrome
- Android4.x Android Browser

### サンプルコード

```html:b.html
<!doctype html>
<html>
    <head>
    <meta charset="utf8">
      <style type="text/css">
      #upload {
        width:90px;
        height:35px;
        text-align:center;
        line-height:35px;
        border-radius:5px;
        color:#fff;
        background-color:#4385bf;
        margin:10px 0;
        cursor:pointer;
      }
      #results {
        border:1px solid #4385bf;
        background-color:#bbd4ea;
        height:160px;
        padding:5px;
        margin:10px 0;
      }
      #results img {
        border:1px solid #000;
        margin:0 5px 0 0;
      }
      #file {
        opacity:0;
      }
      </style>
    <body>
        <form action="index.php" method="post" enctype="multipart/form-data" id="form">
            <input type="file" accept="image/jpeg, image/gif, image/png" id="file" name="files[]" multiple>
            <button type="submit">submit</button>
        </form>
        <div id="upload">picture</div>
        <div id="results"></div>
        <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
        <script>
        $(function() {
            $('#upload').click(function() {
              $('#file').click();
            });

            function setImage() {
              for (var i = 0; i < this.files.length; i++) {
                var file = this.files[i];
                fr = new FileReader();
                fr.onload = function(e) {
                    var img = $('<img>');
                    img.attr('src', e.target.result);
                    img.css('height', '160px');
                    $('#results').append(img);
                };
                fr.readAsDataURL(file);
              }
            }

            $('#file').on('change', setImage);
        });
        </script>
    </body>
</html>
```

もうなんていうかシンプル。

```
<input type="file" accept="image/jpeg, image/gif, image/png" id="file" name="files[]" multiple>
```

で、普通にHTMLとしてinputタグを記述して、multiple指定。これで、Android Browserも動く。個数制限したい場合は、`this.files`のループの中でスキップすれば良い。

# Androidで気をつけること

オリジナルデザインのボタンにするにあたって、元々のファイル参照ボタンを`opacity:0;`で消しているが、これを`display:none;`とか`visibility:hidden;`にすると、Androidではうまく動かない。透明に隠して、その上にデザイン要素を載せるのがベターっぽい。