Help us understand the problem. What is going on with this article?

iOS、Safariの<input type="file" multiple>で発生するエラーに対応した際の備忘録

More than 1 year has passed since last update.

はじめに

とあるサイトを制作していた時、iOS、Safariの<input type="file" multiple>でファイルを選択した場合にエラーが発生する事象に遭遇しました。
ネット上で有益な情報を見つけられなかったので、備忘録として残します。

事象

下記のような、選択したファイル名をリスト表示させるものを作る際に発生しました。

ソース

HTML
<form action="" method="get">
<div>
<ul>
</ul>
<label>
<input type="file" multiple="multiple">
</label>
</div>
</form>
JavaScript
$(function () {
  /*------------------------------------------
    ファイルアップロード
  --------------------------------------------*/ 
  $('body').on('change', 'input[type="file"]', function() {
    var $this = $(this)
    var $ul = $this.parent('label').siblings('ul')
    var listHtml = ''

    if($ul.length > 0) {
      var files = $this[0].files
      var filesLength = files.length

      // ファイルを1件以上選択している場合
      if(filesLength > 0) {
        // HTMLを生成
        for(var i = 0; i < filesLength; i++) {
          listHtml += '<li>' + files[i].name + '</li>'
        }

        // HTMLに反映
        $ul.html(listHtml)
      } else {
        // ファイルの選択をキャンセルした場合
        // リストを削除する
        $ul.html('')
      }
    }
  })
})

See the Pen MultipleFileSample01 by N/NE (@inumberx) on CodePen.

発生端末

OS iOS 12.1.4
端末 iPhoneX
ブラウザ Safari、Google Chrome

現象

<input type="file" multiple>でファイルを複数選択した後に、再度ファイルを再選択・キャンセルすると下記画像のようなエラーが発生するというものです。

img_001.jpg

  • 問題が発生したため、このWebページが再読み込みされました。というメッセージが表示される

img_002.jpg

  • "https://XXX/YYY/ZZZ"で問題が繰り返し起きました。というメッセージが表示される

発生タイミング

色々と検証した結果、エラーは下記のようなタイミングで発生しているようでした。

  • ファイル選択済の状態でファイルを再選択する
  • ファイル選択済の状態でキャンセルする
  • ファイルを選択した時に、既にファイル選択済の他の<input type="file" multiple>が表示状態(display:none;以外)で存在する

対応方法

エラーを発生させないためには、ファイルを選択した時に既にファイル選択済の<input type="file" multiple>がdisplay:none;以外で存在していてはいけません。
そこで、ファイルを選択した時にJS(jQuery)でその要素を非表示にし、新しく空の<input type="file" multiple>を作ることによってエラーが発生しないようにしました。

ソース

HTML
<form action="" method="get">
<div>
<ul>
</ul>
<label>
<input type="file" multiple="multiple">
</label>
</div>
</form>
CSS
input[type="file"][multiple="multiple"].ios {
position: absolute;
opacity: 1;
z-index: 1;
}
input[type="file"][multiple="multiple"].ios + input[type="file"][multiple="multiple"] {
display: none;
}
JavaScript
$(function () {
  /*------------------------------------------
    ファイルアップロード
  --------------------------------------------*/
  // iOSの場合
  // すでにファイルを選択済みの<input type="file" multiple>の場合、ファイルを再選択・キャンセルした時にブラウザのエラーが発生するので個別対応を行う
  if(isIOs && $('input[type="file"][multiple="multiple"]').length > 0) {
    var $file = $('input[type="file"][multiple="multiple"]')
    var fileLength = $file.length

    for(var i = 0; i < fileLength; i++) {
      var $thisFile = $($file[i])
      // inputを複製
      var $cloneFile = $thisFile.clone(true)
      // 複製した要素にクラスを付与
      $cloneFile.val('').addClass('ios')
      // 複製した要素はabsoluteで複製元の要素に被せるため、親要素をrelativeにする
      $thisFile.parent().css(
        {
          'position': 'relative',
          'min-width': $thisFile.outerWidth(true) + 'px',
          'min-height': $thisFile.outerHeight(true) + 'px'
        }
      );
      // 複製した要素をDOMに追加
      $thisFile.before($cloneFile)
    }

    // 複製した要素に隣り合う<input type="file" multiple>がタッチされた時の処理
    $('body').on('touchstart', 'input[type="file"][multiple="multiple"].ios + input[type="file"][multiple="multiple"]', function() {
      // 何もしない
      return false
    })

    // サブミット時の処理
    $('body').on('submit', function() {
      // 複製した要素を削除する
      $('input[type="file"][multiple="multiple"].ios').remove()
    })
  }

  $('body').on('change', 'input[type="file"]', function() {
    var $this = $(this)
    var $ul = $this.parent('label').siblings('ul')
    var listHtml = ''

    if($ul.length > 0) {
      var files = $this[0].files
      var filesLength = files.length

      // ファイルを1件以上選択している場合
      if(filesLength > 0) {
        // HTMLを生成
        for(var i = 0; i < filesLength; i++) {
          listHtml += '<li>' + files[i].name + '</li>';
        }

        // HTMLに反映
        $ul.html(listHtml)
      } else {
        // ファイルの選択をキャンセルした場合
        // リストを削除する
        $ul.html('')
      }
    }

    // iOSの場合
    if($this.hasClass('ios')) {
      // inputを複製
      var $cloneFile = $this.clone(true)
      // 複製した要素のvalueを空にする
      $cloneFile.val('')
      // すでに存在するinputを削除する
      $this.siblings('input[type="file"][multiple="multiple"]').remove()
      // 複製した要素をDOMに追加
      $this.removeClass('ios').before($cloneFile)
    }
  })
})

/*------------------------------------------
 iOSチェック
--------------------------------------------*/
function isIOs() {
  var ua = navigator.userAgent
  var bw = window.navigator.userAgent.toLowerCase()

  // iOSの場合
  if(ua.indexOf('iPhone') > 0 || ua.indexOf('iPod') > 0 ) {
    return true
  }

  return false
}

See the Pen MultipleFileSample02 by N/NE (@inumberx) on CodePen.

問題点

  • 常に「ファイル未選択」と表示される → 選択したファイル数を「ファイル未選択」の文言の上に無理やり被せる?
  • 根本的な解決方法ではない気がする → もっと良い解決方法をご存知の方がいらっしゃったらご教示ください
inumberx
Webデザイナー・フロントエンドエンジニアとして働いています。
https://afterworks.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away