はじめに

HTML5/JavaScript でファイルのドラッグ&ドロップ、画像ファイルのプレビューの実装方法。

TL;DR

この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. やりたいこと
    2. 完成イメージ
    3. ざっくり解説
  5. その他・メモ
  6. まとめ
  7. 参考文献

環境・条件

以下環境で確認

  • MacOS Catalina v10.15.7(19H2)
  • Chrome バージョン: 86.0.4240.111(Official Build)(x86_64)

詳細

参考:

やりたいこと

  • 複数の画像ファイルを POST したい
  • 画像ファイルの設定方法1: ドラッグ&ドロップ
  • 画像ファイルの設定方法2: ドラッグ&ドロップ 領域のクリック (ファイル選択ダイアログ開く)
  • 画像ファイルの設定方法3: ファイル選択ボタンのクリック (ファイル選択ダイアログ開く)
  • (諸事情で)ファイル選択やドラッグ&ドロップは1ファイルずつに制限

完成イメージ

簡素化のために <form method="POST" enctype="multipart/form-data"> は省略。


CodePen で見ないとドラッグ&ドロップ上手く動かないかも。

[HTML/JS] ドラッグ&ドロップ ファイル読み込み

See the Pen [HTML/JS] ドラッグ&ドロップ ファイル読み込み by 17num (@17num) on CodePen.


HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="drag-and-drop-area">
<div>
ドラッグ&ドロップ or クリック
</div>
</div>

<h4>プレビュー(& input)</h4>
<div id="previews"></div>

<div id="file-select">
<input type="file" class="hidden" accept="image/*" id="file-select-input">
<button type="button" id="file-select-button">
ファイル選択
</button>
</div>

SCSS(CSS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#drag-and-drop-area {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid gray;
width: 500px;
height: 300px;
cursor: pointer;

&.active {
background-color: lightyellow;
}
}

.hidden {
display: none;
}

#previews {
border: 1px solid gray;
min-height: 10px;
background-color: lightblue;
margin-bottom: 10px;
img {
max-width: 300px;
}
}

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// ファイル選択ボタン, ドラッグ&ドロップ領域のクリックでファイル選択ダイアログ
document.querySelectorAll('#file-select-button, #drag-and-drop-area').forEach((ele) => {
ele.addEventListener('click', () => {
document.getElementById('file-select-input').click();
});
});

// ファイル選択後
document.getElementById('file-select-input').addEventListener('change', (event) => {
const files = event.target.files;
if (files.length > 0) {
previewAndInsert(files);
}
event.target.files = null;
event.target.value = null;
});

const dragAndDropArea = document.getElementById('drag-and-drop-area');

// ドラッグ中
dragAndDropArea.addEventListener('dragover', (event) => {
dragAndDropArea.classList.add('active');
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
});

// マウスがドラッグ&ドロップ領域外に出たとき
dragAndDropArea.addEventListener('dragleave', (event) => {
dragAndDropArea.classList.remove('active');
});

// ドロップ時
dragAndDropArea.addEventListener('drop', (event) => {
event.preventDefault();
dragAndDropArea.classList.remove('active');
const files = event.dataTransfer.files;
if (files.length === 0) {
return;
}

// 画像ファイルのみ OK
if (!files[0].type.match(/image\/*/)) {
return;
}

previewAndInsert(files);
});

// 画像プレビューと input 追加
const previewAndInsert = (files) => {
const file = files[0];

const wrapper = document.createElement('div');

const input = document.createElement('input');
input.type = 'file';
input.classList.add('hidden');
// https://qiita.com/jkr_2255/items/1c30f7afefe6959506d2
if (files.length > 1 && typeof DataTransfer !== 'undefined') {
const dt = new DataTransfer();
dt.items.add(files[0]);
input.files = dt.files;
} else {
input.files = files;
}
wrapper.appendChild(input);

const img = document.createElement('img');
const reader = new FileReader();
reader.onload = (event) => {
img.src = event.target.result;
updateHTMLCode();
}
reader.readAsDataURL(file);
wrapper.appendChild(img);

document.getElementById('previews').appendChild(wrapper);
}

ざっくり解説

ポイント箇所のみ簡単にまとめ。基本的には参考ページに書かれている通り。

  • ドラッグ&ドロップ 使いたい場合は、dragover, drop に対して preventDefault() が必要
    • 無いと別タブでファイルが開くなど、ブラウザ標準イベントが発火するため
  • dataTransfer.dropEffectcopy に設定することで、ファイルドロップ時にファイルを参照できる
    • これは理解が浅いので間違ってるかも
  • ドロップされたファイルは drop 内で event.dataTransfer.files で参照可能
  • 画像プレビューは FileReaderreadAsDataURL を使う
  • ドラッグ中の UI 変更(色変えるとか) は dragover イベント内で実施
  • 1つの input に 1つのファイルにするために、DataTransfer を生成して、DataTranfer.itemsadd を利用
    • 複数ファイルドロップされたものをそのまま input.files = event.dataTransfer.files とすると複数ファイル選択された状態になる
  • クリック時のファイル入力にも対応するために <input type="file"> も設置
    • ボタンなどクリック → file 入力フィールドクリック → 選択ダイアログ開く の流れ
    • ファイル選択後、プレビューとともに新規 input が生成されるので、元々設置していた方は value, files をクリア (<form> での送信対象から削るため)

その他・メモ

特殊な要件が無ければ既存ライブラリを使うのも手。今回は要件がややこしく、ライブラリで実現できるのか不明(調査に時間かかる)だったので、ガッツリ自作した感じ。

まとめ

参考文献

関連記事

この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list