Vue.jsでDrag and DropでMultipleなFileUploadモジュールを作りました。
gifのような動作でファイルのアップロードができるUIです。

上記のイメージのように、このFileUploadUIでは下記の機能をサポートしました。
ファイル選択(複数)
Drag and Drop(複数)
ファイル追加
ファイル削除
このサンプルでは送信ボタンを押下したときにファイルのサーバへのアップロードが開始されます。
今後Drag and Drop時にサーバーへアップロードする機能を追加する予定です。
デモ
Drop files or Browse
ソースコード
file_upload.html
file_upload.js
file_upload.css
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet" />
<div id="drag_drop_app" class="file_uploader_module">
<input type="file" ref="file_upload" style="display: none" @change="onFileChange" multiple>
<div class="drop_area" @dragenter="dragEnter" @dragleave="dragLeave" @dragover.prevent @drop.prevent="dropFile"
:class="{enter: isEnter}" type="button" @click="$refs.file_upload.click()">
<i class="fas fa-arrow-circle-up text-6xl mb-3 fa-5x upload-icon"></i>
<b>Drop files or Browse</b>
</div>
<ul class="flex">
<li class="flex-col" v-for="(file,index) in files" :key="index" @click="deleteFile(index)">
<div style="position: relative;">
</div>
<div class="file-box" style="display: block;">
<span>
{{ file.name }}
<i class="fa fa-times delete-mark"></i>
</span>
</div>
</li>
</ul>
<div v-show="files.length">
<button class="button" @clikc="sendFile">Send</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet" />
<div id="drag_drop_app" class="file_uploader_module">
<input type="file" ref="file_upload" style="display: none" @change="onFileChange" multiple>
<div class="drop_area" @dragenter="dragEnter" @dragleave="dragLeave" @dragover.prevent @drop.prevent="dropFile"
:class="{enter: isEnter}" type="button" @click="$refs.file_upload.click()">
<i class="fas fa-arrow-circle-up text-6xl mb-3 fa-5x upload-icon"></i>
<b>Drop files or Browse</b>
</div>
<ul class="flex">
<li class="flex-col" v-for="(file,index) in files" :key="index" @click="deleteFile(index)">
<div style="position: relative;">
</div>
<div class="file-box" style="display: block;">
<span>
{{ file.name }}
<i class="fa fa-times delete-mark"></i>
</span>
</div>
</li>
</ul>
<div v-show="files.length">
<button class="button" @clikc="sendFile">Send</button>
</div>
</div>
const drag_drop_app = new Vue({
el: "#drag_drop_app",
data: {
isEnter: false,
files: []
},
methods: {
dragEnter() {
this.isEnter = true;
},
dragLeave() {
this.isEnter = false;
},
dragOver() {
},
dropFile() {
this.files.push(...event.dataTransfer.files)
this.isEnter = false;
},
deleteFile(index) {
this.files.splice(index, 1)
},
upload: function () {
// FormData を利用して File を POST する
let formData = new FormData();
formData.append('files', this.uploadFile);
let config = {
headers: {
'content-type': 'multipart/form-data'
}
};
axios
.post('yourUploadUrl', formData, config)
.then(function (response) {
})
.catch(function (error) {
})
},
sendFile() {
this.files.forEach(file => {
let form = new FormData()
form.append('file', file)
axios.post('url', form).then(response => {
console.log(response.data)
}).catch(error => {
console.log(error)
})
})
},
btnclick() {
this.$refs.input.click();
},
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
this.files.push(...files)
console.log(files)
}
}
})
const drag_drop_app = new Vue({
el: "#drag_drop_app",
data: {
isEnter: false,
files: []
},
methods: {
dragEnter() {
this.isEnter = true;
},
dragLeave() {
this.isEnter = false;
},
dragOver() {
},
dropFile() {
this.files.push(...event.dataTransfer.files)
this.isEnter = false;
},
deleteFile(index) {
this.files.splice(index, 1)
},
upload: function () {
// FormData を利用して File を POST する
let formData = new FormData();
formData.append('files', this.uploadFile);
let config = {
headers: {
'content-type': 'multipart/form-data'
}
};
axios
.post('yourUploadUrl', formData, config)
.then(function (response) {
})
.catch(function (error) {
})
},
sendFile() {
this.files.forEach(file => {
let form = new FormData()
form.append('file', file)
axios.post('url', form).then(response => {
console.log(response.data)
}).catch(error => {
console.log(error)
})
})
},
btnclick() {
this.$refs.input.click();
},
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
this.files.push(...files)
console.log(files)
}
}
})
/* NEW FILE DRAG AND DROP MODULE */
:root {
--background-color:#f3f3f3;
--background-color-hover:#b9b9b9;
--border-color:#6d6d6d;
--border-color-hover:#757575;
--line-border: 2px;
--text-color-dark: #505050;
}
.file_uploader_module {
margin: 15px;
}
.drop_area {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
max-width: 700px;
min-height: 150px;
border: 5px dashed var(--border-color);
background-color:var(--background-color);
color: var(--text-color-dark);
border-radius: 20px;
box-sizing: border-box;
transition: background-color 160ms ease;
font-weight:bold;
}
.enter {
border: 5px dashed var(--border-color-hover);
background-color: var(--background-color-hover);
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
}
.flex {
display: flex;
align-items: center;
}
.flex-col {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.5em;
font-size: 10px;
}
.button {
padding: 0.5em 1.5em;
background-color:var(--background-color);
color: var(--text-color-dark);
font-size: 14px;
font-weight: bold;
border-radius: 5px;
border: 3px solid var(--border-color);
}
.upload {
color :var(--text-color-dark);
}
.file-box{
font-size: 15px;
padding: 2px 8px;
border: 3px solid var(--border-color);
background-color:var(--background-color);
color: var(--text-color-dark);
font-weight:bold;
}
.delete-mark {
font-size: 20px;
color: var(--text-color-dark);
padding-left:20px;
vertical-align: middle;
}
/* NEW FILE DRAG AND DROP MODULE */
:root {
--background-color:#f3f3f3;
--background-color-hover:#b9b9b9;
--border-color:#6d6d6d;
--border-color-hover:#757575;
--line-border: 2px;
--text-color-dark: #505050;
}
.file_uploader_module {
margin: 15px;
}
.drop_area {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
max-width: 700px;
min-height: 150px;
border: 5px dashed var(--border-color);
background-color:var(--background-color);
color: var(--text-color-dark);
border-radius: 20px;
box-sizing: border-box;
transition: background-color 160ms ease;
font-weight:bold;
}
.enter {
border: 5px dashed var(--border-color-hover);
background-color: var(--background-color-hover);
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
}
.flex {
display: flex;
align-items: center;
}
.flex-col {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.5em;
font-size: 10px;
}
.button {
padding: 0.5em 1.5em;
background-color:var(--background-color);
color: var(--text-color-dark);
font-size: 14px;
font-weight: bold;
border-radius: 5px;
border: 3px solid var(--border-color);
}
.upload {
color :var(--text-color-dark);
}
.file-box{
font-size: 15px;
padding: 2px 8px;
border: 3px solid var(--border-color);
background-color:var(--background-color);
color: var(--text-color-dark);
font-weight:bold;
}
.delete-mark {
font-size: 20px;
color: var(--text-color-dark);
padding-left:20px;
vertical-align: middle;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" rel="stylesheet" /> <div id="drag_drop_app" class="file_uploader_module"> <input type="file" ref="file_upload" style="display: none" @change="onFileChange" multiple> <div class="drop_area" @dragenter="dragEnter" @dragleave="dragLeave" @dragover.prevent @drop.prevent="dropFile" :class="{enter: isEnter}" type="button" @click="$refs.file_upload.click()"> <i class="fas fa-arrow-circle-up text-6xl mb-3 fa-5x upload-icon"></i> <b>Drop files or Browse</b> </div> <ul class="flex"> <li class="flex-col" v-for="(file,index) in files" :key="index" @click="deleteFile(index)"> <div style="position: relative;"> </div> <div class="file-box" style="display: block;"> <span> {{ file.name }} <i class="fa fa-times delete-mark"></i> </span> </div> </li> </ul> <div v-show="files.length"> <button class="button" @clikc="sendFile">Send</button> </div> </div>
const drag_drop_app = new Vue({ el: "#drag_drop_app", data: { isEnter: false, files: [] }, methods: { dragEnter() { this.isEnter = true; }, dragLeave() { this.isEnter = false; }, dragOver() { }, dropFile() { this.files.push(...event.dataTransfer.files) this.isEnter = false; }, deleteFile(index) { this.files.splice(index, 1) }, upload: function () { // FormData を利用して File を POST する let formData = new FormData(); formData.append('files', this.uploadFile); let config = { headers: { 'content-type': 'multipart/form-data' } }; axios .post('yourUploadUrl', formData, config) .then(function (response) { }) .catch(function (error) { }) }, sendFile() { this.files.forEach(file => { let form = new FormData() form.append('file', file) axios.post('url', form).then(response => { console.log(response.data) }).catch(error => { console.log(error) }) }) }, btnclick() { this.$refs.input.click(); }, onFileChange(e) { let files = e.target.files || e.dataTransfer.files; this.files.push(...files) console.log(files) } } })
/* NEW FILE DRAG AND DROP MODULE */ :root { --background-color:#f3f3f3; --background-color-hover:#b9b9b9; --border-color:#6d6d6d; --border-color-hover:#757575; --line-border: 2px; --text-color-dark: #505050; } .file_uploader_module { margin: 15px; } .drop_area { display: flex; justify-content: center; align-items: center; flex-direction: column; max-width: 700px; min-height: 150px; border: 5px dashed var(--border-color); background-color:var(--background-color); color: var(--text-color-dark); border-radius: 20px; box-sizing: border-box; transition: background-color 160ms ease; font-weight:bold; } .enter { border: 5px dashed var(--border-color-hover); background-color: var(--background-color-hover); } ul { margin: 0; padding: 0; list-style-type: none; } .flex { display: flex; align-items: center; } .flex-col { display: flex; flex-direction: column; align-items: center; margin: 0.5em; font-size: 10px; } .button { padding: 0.5em 1.5em; background-color:var(--background-color); color: var(--text-color-dark); font-size: 14px; font-weight: bold; border-radius: 5px; border: 3px solid var(--border-color); } .upload { color :var(--text-color-dark); } .file-box{ font-size: 15px; padding: 2px 8px; border: 3px solid var(--border-color); background-color:var(--background-color); color: var(--text-color-dark); font-weight:bold; } .delete-mark { font-size: 20px; color: var(--text-color-dark); padding-left:20px; vertical-align: middle; }