本文へスキップ

ドラッグ&ドロップ アップロード

点線の枠にファイルをドラッグするか、クリック/タップで選択。ファイル名と疑似プログレスバーで進捗を表示する。

ENGLISH

Drag & Drop File Upload

  • JS
153基本のUI部品 / フォーム入力 の一覧 →

プレビュー

ドラッグ

操作ドラッグ/スワイプで操作できます(PC・スマホ対応)。

使いどころ

ドラッグ操作が使えるデスクトップ環境ではドラッグの手軽さを、タッチ端末ではタップでのファイル選択を、同じゾーンで両立させたい単一ファイルのアップロードに向く。この実装はドロップでもタップでも同じ .fu-zone が受け口になっており、ドラッグに対応していない端末でも操作の入口が失われない設計になっている。反面、実際のアップロード進捗はsetIntervalで作った疑似的なランダム増加であり、本物の通信の進み具合とは一切連動していない。実運用でXHRやfetchの進捗イベントに差し替える前提のプレースホルダーとして扱うべきで、このまま本番に出すと「進捗バーが100%になったのに実際のアップロードはまだ終わっていない」ような食い違いが起こり得る。また dataTransfer.files[0] / input.files[0] のみを読んでおり、複数ファイルの同時ドロップには対応していない。

仕組み

見た目を作る .fu-zone(role="button" tabindex="0")と、実際のファイル選択を担う input[type=file] を同じ位置に重ねている。inputは position: absolute; inset: 0; opacity: 0 で完全に透明化された状態でゾーンの上に載っており、マウスでゾーンをクリックするとその透明なinputに直接ヒットしてOS標準のファイル選択ダイアログが開く。一方でinputにはaria-hidden="true"とtabindex="-1"が付けられてTab移動の対象から外されており、キーボードユーザーは代わりに.fu-zone自体にフォーカスが当たり、Enter/Spaceのkeydownハンドラでinput.click()を呼ぶことで同じダイアログを開く。ドラッグ&ドロップはdragover(毎回preventDefault必須。呼ばないとブラウザがドロップ自体を拒否する)、dragleave、dropの3イベントで完結しており、input要素とは独立して動く別経路である。

実装の注意

複数ファイルを一度にドロップしても最初の1つしか処理されないのに、ユーザーには何も言われずに1つだけ処理されたようにしか見えず、残りが無視されたことを示すメッセージが無い。次に、dragoverでpreventDefault()を呼び忘れる、あるいは一度だけ呼んで以降呼ばなくなるような書き換えをすると、ブラウザはドロップ操作自体を拒否してしまい、外見上はゾーンにファイルを乗せているのに何も起きないという分かりにくい壊れ方になる。疑似進捗のMath.random()による増加は見た目としては自然だが、本物の通信に差し替える際は進捗の駆動源そのものを丸ごと入れ替える必要があり、失敗時の再試行・エラー表示の状態遷移もこのコードには一切用意されていない。ファイル形式やサイズの事前検証も無く、accept属性はファイル選択ダイアログのフィルタとしてしか働かずドラッグ&ドロップには効かないため、ドロップ経路でも別途JS側でチェックする必要がある。

アクセシビリティ

aria-hidden かつ tabindex="-1" にした本物のinputと、role="button" tabindex="0" のゾーンで役割を分けている構成は、キーボードユーザーに1つのタブストップだけを提供しつつマウス/タッチユーザーにはネイティブのファイル選択・ドラッグの挙動をそのまま使わせる、意図的で妥当な設計である。ただしこれはkeydownハンドラ(Enter/Spaceでinput.click())が生きていて初めて成立する設計であり、もしこのハンドラだけ削除されると、キーボードユーザーにとってゾーンは押しても何も起きない要素になり、実質的にファイルを選ぶ手段を失う。アップロード結果を表示する#fu-resultにはaria-liveが無く、「アップロード中…」から「完了!」への変化はスクリーンリーダー利用者には自動的に伝わらない。進捗バー自体もdivにインラインwidthを設定しているだけで、role="progressbar"やaria-valuenowを持たないため、支援技術は数値としての進捗を一切取得できない。

調整して使う

複数ファイル対応(dataTransfer.files/ input.files を全件ループし、ファイルごとに行と進捗バーを持たせる)が最も需要のある拡張である。疑似進捗を本物のXHR upload.onprogressに差し替える改修、画像アップロード向けにaccept="image/*"とサムネイルプレビューを追加する改修、一度選択したファイルを取り消して選び直すキャンセル操作の追加なども考えられる。エラー時の状態(ネットワーク断・サーバー拒否)を#fu-statusに反映する分岐も、現状は成功パスしか用意されていないため別途必要になる。

コード(コピーして使えます)

<div class="fu-wrap">
  <label class="fu-label">ファイルをアップロード</label>
  <div class="fu-zone" id="fu-zone" tabindex="0" role="button" aria-label="ファイルを選択またはドロップ">
    <div class="fu-icon" aria-hidden="true">
      <svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#2b50c7" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
        <polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/>
        <path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/>
      </svg>
    </div>
    <p class="fu-text">ここにドロップ</p>
    <p class="fu-sub">またはタップして選択</p>
    <input id="fu-input" class="fu-hidden-input" type="file" aria-hidden="true" tabindex="-1" />
  </div>
  <div class="fu-result" id="fu-result" hidden>
    <p class="fu-filename" id="fu-filename"></p>
    <div class="fu-bar-wrap">
      <div class="fu-bar" id="fu-bar"></div>
    </div>
    <p class="fu-status" id="fu-status">アップロード中…</p>
  </div>
</div>

タグ

同じカテゴリ「フォーム入力」のほかのパーツ

関連パーツ