【vue.js】ドラッグ&ドロップでファイルアップロード
vue.jsを使ってアプリケーションを構築する際にドラッグ&ドロップでファイルのアップロードを行いたい時、ライブラリなしでコードを書きたいと思ったことはありませんか?本文書はそんな人のためにvue.js入門者でもできるドラッグ&ドロップのファイルアップロードについて基礎から説明を行っていきます。今までライブラリに頼って一度もドラッグ&ドロップの自作にチャレンジしていない人であればこんなに簡単なの!と驚くこと間違いなしです。
目次
初期設定
手元の環境ですぐに動作確認ができるようにvue.jsはcdnを利用します。任意のフォルダを作成し、index.htmlファイルを作成してください。
index.htmlファイルに以下のコードを記述しファイルをドロップする四角枠を作成します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue.js Drag&Drop File Upload</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
html,
body {
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.drop_area {
color: gray;
font-weight: bold;
font-size: 1.2em;
display: flex;
justify-content: center;
align-items: center;
width: 500px;
height: 300px;
border: 5px solid gray;
border-radius: 15px;
}
</style>
<body>
<div id="app">
<div class="drop_area">
ファイルアップロード
</div>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
}
})
</script>
</body>
</html>
比較的長いCSSの設定になっていますが中身は単純でFlexboxを利用して画面の中央にファイルをドロップするためのターゲットとなる500px☓300pxの四角枠を作成しているだけです。
dragenter, dragleaveイベントの動作確認
ドラッグ&ドロップを使ったアプリケーションを構築するためにはHTML5が持つdrag&dropに関するイベント利用します。本文書では4つのイベントを利用するので一つ一つどのようなイベントなのか説明を行っていきます。
dragenterイベントの動作確認
ファイルアップロード機能を持っている一般的なアプリケーションではドロップエリアまたはブラウザに向けてファイルをドラッグすると画像の色が変わるものをよく見かけます。同じようにvue.jsのdragenterイベントとclassバインドを利用して中央の四角い枠の色を動的に変更します。
ドロップエリアのdivタグにdragenterイベントを追加しdragEnterメソッドを設定します。
<div class="drop_area" @dragenter="dragEnter">
ファイルアップロード
</div>
vue.jsにdragEnterメソッドを追加します。動作確認なのでdragenterイベントが発生したらコンソールにEnter Drop Areaの文字列を表示させます。
methods: {
dragEnter() {
console.log('Enter Drop Area');
}
}
ブラウザ上のドロップエリアにファイルをドラッグしてください。Chromeであればデベロッパーツールのコンソールに”Enter Drop Area”が表示されれば正常に設定が行われています。
classバインドによる動的な適用
classバインドを行うためにデータプロパティisEnterを追加します。isEnterはデフォルトではfalseに設定し、dragEnterイベントが発生するとtrueに変更します。
const app = new Vue({
el: "#app",
data: {
isEnter: false
},
methods: {
dragEnter() {
this.isEnter = true;
}
}
})
ドロップエリアのdivにclassバインドを追加します。下記の設定ではisEnterの値がtrueの場合にクラスのenterが適用されます。falseの場合にはclassのenterは適用されません。isEnterの値によりclassの適用、非適用を切り替えます。
<div class="drop_area"
@dragenter="dragEnter"
:class="{enter: isEnter}">
ファイルアップロード
</div>
styleタグの中にクラスのenterを追加します。ボーダーの色と太さと形状を設定しています。
.enter {
border: 10px dotted powderblue;
}
ブラウザ上のドロップエリアにファイルをドラッグしてください。四角い枠に入った瞬間にenterクラスが適用されドロップエリアの枠が変わります。
一度変わってしまうと解除する設定を行っていないのでブラウザをリロードしない限りはドットの枠線がついたままになります。
Dragleaveイベントの動作確認
dragenterではドロップエリアに入った場合、次はドロップエリアの外に出た時に発生するdragleaveイベントを設定します。
<div class="drop_area"
@dragenter="dragEnter"
@dragleave="dragLeave"
:class="{enter: isEnter}">
ファイルアップロード
</div>
dragleaveイベントに指定したdragLeaveメソッドをvue.jsに追加します。dragEnterとは逆でデータプロパティisEnterの値をfalseに設定します。
const app = new Vue({
el: "#app",
data: {
isEnter: false
},
methods: {
dragEnter() {
this.isEnter = true;
},
dragLeave() {
this.isEnter = false;
},
}
})
dragenterとdragleaveが設定できればファイルをドロップエリアにドラッグすればボーダーがドットの枠線になり、ドロップエリアの外に出ると枠線が実線の元の線に戻ります。
Dragoverイベントについて
DropEnterとDropLeaveの他にドロップエリアの上にファイルをドラッグした場合に発火されるDragoverイベントがあります。
ドロップエリアのdivタグにdragoverイベントを追加してdragOverメソッドを設定します。
<div class="drop_area"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover="dragOver"
:class="{enter: isEnter}">
ファイルアップロード
</div>
dragOverメソッドではイベントが発生するとコンソールにDragOverという文字列を表示させます。
dragOver() {
console.log('DragOver')
},
ファイルをドロップエリアの上に持っていくとエリアの上にいる間ずっとイベントが発生します。
左の110がイベントが発生した回数です。
Dropイベントの動作確認
最後に説明するDropイベントを利用するとドロップエリアにドロップしたファイルの情報を取得することができます。
ドロップエリアのdivにdropイベントを追加し、dropFileメソッドを設定します。
<div class="drop_area"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover="dragOver"
@drop="dropFile"
:class="{enter: isEnter}">
ファイルアップロード
</div>
dropFileメソッドではイベントが発生するとコンソールにDropped Fileの文字列が表示されます。
dropFile() {
console.log('Dropped File')
}
dragenter, dragleave, dragover, dropの4つのイベントが設定を行いました。しかしファイルをドロップエリアにドロップしてもコンソールにDropped Fileの文字列が表示されるわけではなくドロップした画像がそのままブラウザに表示されます。
preventDefaultの設定
ブラウザにファイルをドロップするとドロップしたファイルの内容が画面に表示されるというのはデフォルトの動きです。このデフォルトの動作を停止するためにpreventDefaultという設定が準備されており、vue.jsではイベントにdrop.preventのように.preventを設定を行うことでpreventDefaultを利用することができます。
ファイルをドロップするためにはdropoverとdropにpreventを設定する必要があります。
<div class="drop_area"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover.prevent
@drop.prevent="dropFile"
:class="{enter: isEnter}">
ファイルアップロード
</div>
preventを設定した後にファイルをドロップエリアにドロップするとコンソールにDropped Fileが表示されます。
Dropped Fileが表示された後ブラウザを見るとドットの枠線のままの状態になっています。
それはファイルをDropしたため、ファイルが枠外に出ていないのでdragleaveイベントが発生していないためです。ファイルをDropした後に枠線が実線に戻るようにDropFileメソッドでisEnterをfalseにしておきます。これでファイルをドロップするとドット線が実線に変わります。
dropFile() {
console.log('Dropped File')
this.isEnter = false;
}
ファイル情報を取得する
イベントからファイル情報を取得
ファイルをドロップエリアにドロップできることが確認できたので次はドロップしたファイル情報を取得します。
取得にはイベントを利用しevent.dataTransfer.filesでファイル情報を取得することができます。
dropFileメソッドの中でファイル情報を取得してみましょう。
dropFile() {
console.log(event.dataTransfer.files)
this.isEnter = false;
}
ファイルをドロップエリアにドロップしてコンソールを確認するとFileListオブジェクトにファイルの情報が入っていることが確認できます。
ファイル名はlog.pngで、サイズが3451であることやファイルのtypeがpngであることもわかります。
ファイル情報をvue.jsで管理
取得したファイル情報をvue.jsで管理するためにデータプロパティのfilesを追加します。
data: {
isEnter: false,
files: []
},
dropFileメソッドで取得したファイル情報をfilesに保存します。保存する時はスプレッド構文を利用して保存します。
dropFile() {
this.files = [...event.dataTransfer.files]
this.isEnter = false;
}
これでドロップしたファイルはfilesに保存されるためvue.js上で扱うことができます。
v-forでファイル情報を表示
vue.jsのデータプロパティfilesに保存されたファイル情報をv-forを利用して展開しブラウザ上に表示します。
<div id="app">
<div class="drop_area" @dragenter="dragEnter" @dragleave="dragLeave" @dragover.prevent @drop.prevent="dropFile"
:class="{enter: isEnter}">
ファイルアップロード
</div>
<div>
<ul>
<li v-for="file in files">{{ file.name }}
</li>
</ul>
</div>
</div>
複数のファイルを同時にドロップエリアにドロップしてみましょう。
ドロップしたファイルの名前がドロップエリアの下に表示されます。
バックエンドへのファイルの送信
ブラウザ上からファイルの情報を取得することができたので、取得したファイルをバックエンドに送信することでサーバ上にファイルを保存することができます。
formDataでファイルを送信
ファイルをバックエンドサーバに送信する際はformDataを利用します。本動作確認ではバックエンドサーバを準備していませんが、axiosを利用すると下記のコードでファイル情報をサーバに送信することができます。
dropFile() {
this.files = [...event.dataTransfer.files]
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)
})
})
this.isEnter = false;
}
アップロードするファイル選択
ここからはvue.jsに関連するフロントエンド側の処理についてもう少し深く説明を行っていきます。
ファイルアイコン画像を設定
見栄えを変えるためにファイル名だけではなくファイル名とファイルのアイコン画像を一緒に表示させます。
index.htmlファイルが保存されているフォルダにimgフォルダを作成しファイル画像icon-file.pngを保存します。v-forの展開を行うliタグの中で画像を設定します。
<div>
<ul>
<li v-for="file in files">
<img src="img/icon-file.png">{{ file.name }}
</li>
</ul>
</div>
ulタグのmarginやpaddingを0に設定します。
ul {
margin: 0;
padding: 0;
list-style-type: none;
}
複数のファイルをドロップエリアにドロップすると下記のように表示されます。
Flexboxを利用してファイルを横並びに調整します。
各タグにclassを追加します。
<ul class="flex">
<li v-for="file in files" class="flex-col">
<img class="file_icon" src="img/icon-file.png">
<span>{{ file.name }}</span>
</li>
</ul>
追加したclassをstyleに追加します。
.flex {
display: flex;
align-items: center;
}
.flex-col {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.5em;
font-size: 10px;
}
ブラウザでファイルをドロップエリアにドロップすると下記のように表示されます。
アップロードするファイル選択
ファイルをドロップエリアにドロップした後にアップロードするファイルを選択できるように削除ボタンを追加します。
既存のimgタグの外側にdivタグを追加し、削除ボタンはspanタグの中に×で記述します。
削除ボタンはdivタグを基準にpositionのabsoluteを利用してファイル画像からの位置を調整します。
<ul class="flex">
<li v-for="file in files" class="flex-col">
<div style="position: relative;">
<span class="delete-mark">×</span>
<img class="file_icon" src="img/icon-file.png">
</div>
<span>{{ file.name }}</span>
</li>
</ul>
新たなclassをstyleに追加し、imgタグの外側に追加したdivにはそのままstyleでpositionのrelativeを設定しています。
.delete-mark {
position: absolute;
top: -14px;
right: -10px;
font-size: 20px;
}
ブラウザで確認するとファイル画像の右上に削除Xが表示されます。
Xをクリックするとアップロードするファイル一覧から削除できるようにクリックイベントを追加します。clickイベントにはdeleteFileメソッドを設定し引数にindexを渡します。
<li class="flex-col" v-for="(file,index) in files" :key="index" @click="deleteFile(index)">
vue.jsのmethodsに新たにdeleteFileメソッドを追加します。受け取ったindexを使ってファイル情報を保存しているfilesからクリックしたファイルを削除しています。
deleteFile(index) {
this.files.splice(index, 1)
}
コード追加後ファイルをドロップし削除マークをクリックするとそのファイルが一覧から削除されていることを確認してください。
この設定によりドロップ後にファイルを選択する機能を追加することができました。
アップロードするファイル追加
ドロップしたファイルを削除する機能は追加できましたが一度ドロップした後に別のファイルをドロップした場合の動作を確認してみましょう。
2つファイルをドロップします。
もう一つファイルを別でドロップします。現在のコードではファイルが追加されるのではなく上書きされてしまいます。
追加できるようにコードの書き換えを行います。
dropFile() {
this.files.push(...event.dataTransfer.files)
this.isEnter = false;
},
コードを書き換えたことでファイルの追加も行うことができるようになります。
ファイル選択後にアップロード
ファイルの削除、追加ができるようになったのでブラウザ上でアップロードするファイルを決めてから送信できるように送信ボタンを追加します。
送信ボタンはファイルがドロップされた時のみ表示させるようにv-showディレクティブを利用します。v-showの表示・非表示はfiles.lengthで配列の長さで判断します。ファイルがない場合はfalseとなり、ボタンは表示されません。
<ul class="flex">
<li class="flex-col" v-for="(file,index) in files" :key="index" @click="deleteFile(index)">
<div style="position: relative;">
<span class="delete-mark">×</span>
<img class="file_icon" src="img/icon-file.png">
</div>
<span>{{ file.name }}</span>
</li>
</ul>
<div v-show="files.length">
<button class="button">送信</button>
</div>
buttonのクラスbuttonもstyleに追加します。
.button {
padding: 0.5em 1.5em;
background-color: #0070a7;
color: white;
font-size: 14px;
font-weight: bold;
border-radius: 5px;
border-color: #0070a7;
}
buttonにはclickイベントを追加し、sendFileメソッドを設定します。
<div v-show="files.length">
<button class="button" @clikc="sendFile">送信</button>
</div>
sendFileメソッドの中身は先程バックエンドへのファイルの送信で作成したコードです。
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)
})
})
}
vue.jsを利用するとライブラリの力を借りなくてもドラッグ&ドロップの機能を実装することができます。