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

上記のイメージのように、このFileUploadUIでは下記の機能をサポートしました。
☑️ファイル選択(複数)
☑️Drag and Drop(複数)
☑️ファイル追加
☑️ファイル削除
このサンプルでは送信ボタンを押下したときにファイルのサーバへのアップロードが開始されます。
今後Drag and Drop時にサーバーへアップロードする機能を追加する予定です。
デモ
Drop files or Browse
-
{{ file.name }}
ソースコード
<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;
}

