Drag and Drop Multiple File Upload UI [Vue.js]

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;
    }

    参考

    【vue.js】ドラッグ&ドロップでファイルアップロード|REFFECT

    SNSでもご購読できます。

    コメントを残す

    *