Vue.jsでドラッグ＆ドロップができるプレビュー機能付きフォームを実装してみた


##こんなやつ

下のgif画像を見ていただければ大体わかると思います。
![drag-and-drop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459175/082a78fb-6108-e1a0-3130-08a1c10584ec.gif)

「画像をドラッグ＆ドロップしてそのままアップロードできたら便利だよなあ」
「しかもプレビュー機能もあったらシャレオツだよなあ」

ってことで作ってみました。

##ソースコード

コーディングにまだ慣れてないので、少々汚い部分もあるかと思いますが、ご了承ください。

```html:test.html
<html>
<head>
	<meta charset="utf-8">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

<!-- ここからスタイル記述 -->
	<style type="text/css">
	.commonStyle{ padding:30px; text-align:center; margin:30px; width:400px;}
	.styleA{border:3px dotted gray;}
	.styleB{border:3px dotted rgba(0,200,0,0.7);}
	.button{border:1px solid green; padding:3px; border-radius:5px; background-color:white;}
	.button p {color:green; margin-top:10px; margin-left:10px; margin-right: 10px;}
	</style>
<!-- ここまでスタイル記述 -->

</head>

<body>

<!-- ここからフォームの部分 -->
<form action="******" method="post" enctype="multipart/form-data">

<div id="upload" class="form-group commonStyle" v-bind:class="{'styleA':styleA, 'styleB':styleB}" @dragover.prevent="changeStyle($event,'ok')" @dragleave.prevent="changeStyle($event,'no')" @drop.prevent="uploadFile($event)">

	<label for="upload_image" class="button">
	<p>画像を選択</p>
	<input id="upload_image" type="file" name="img" @change="uploadFile($event)" style="display:none;" accept="image/*">
	</label>

	<!-- ここからプレビュー機能の部分 -->
	<p>またはここに画像ファイルをドラッグ＆ドロップ</p>
	<img v-show="preview" v-bind:src="preview" style="width:300px;">
	<p v-show="preview"> {{name}} </p>
	<!-- ここまでプレビュー機能の部分 -->

</div>
	<button class="btn btn-success" style="width:400px; margin-left:30px;">アップロード</button>

</form>
<!-- ここまでフォームの部分 -->


<!-- ここからVue.jsの部分 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
new Vue({
	el:'#upload',
	data: {
		preview:'',
		name:'',
		styleA:true,
		styleB:false,
	},
	methods: {
		uploadFile: function(event){
			this.styleA = true;
			this.styleB = false;
			const files = event.target.files ? event.target.files : event.dataTransfer.files;
			const file = files[0];
			const reader = new FileReader();
			reader.onload = event => {
				this.preview = event.target.result;
			};
			reader.readAsDataURL(file);
			this.name = files[0].name;
			document.getElementById("upload_image").files = files;
		},
		changeStyle: function(event,flag){
			if(flag=='ok'){
				this.styleA = false;
				this.styleB = true;
			}else{
				this.styleA = true;
				this.styleB = false;
			}
		},
	},
});
</script>
<!-- ここまでVue.jsの部分 -->

</body>
</html>
```

formタグのactionを設定すれば、あとはボタンを押すだけでアップロードされます。

僕は、アップロード後の処理（バリデーション等）はLaravel側に任せております。

次は、このコードで重要な部分を解説します

##ざっくり解説

###1 : @dragover.prevent

divタグ内に記述されている@dragover.prevent="changeStyle($event,'ok')"の部分です。

dragoverは簡単に言えば、「（画像を枠外からドラッグしてきて、）要素の枠内に入った時に発生するイベント」です。

@dragover.preventの.preventって何？って感じですが、これを付けないとブラウザに画像が表示されて終わります。実際にやってみればわかります。

今回、@dragover.prevent="changeStyle($event,'ok')"は「点線の枠を緑色にする」という処理をしております。

ページ上部のgif画像を見てみて下さい。画像が点線の枠内にドラッグされた時にその点線の枠線が緑色になっていることがわかります。

###2 : @dragleave.prevent

@dragleave.prevent="changeStyle($event,'no')"の部分です。

dragleaveはdragoverとは逆で、ドラッグしながら枠内から枠外へと離れて行った時に発生するイベントです。

###3 : @drop.prevent

これは文字通り、ドロップした時に発生するイベントです。

@drop.prevent="uploadFile($event)"により、画像がドロップされるとuploadFile関数が実行されます。

###4 : ＠change

input内の@change="uploadFile($event)"の部分です。

これは、inputの内容が変更された時にuploadFile関数が実行されるということです。

uploadFile関数は、画像データと画像ファイル名を取得して、それぞれthis.previewとthis.nameに格納しているのがわかりますね。

##最後に

・IEへの対応ははできていないと思います。

・僕は正常に動作しましたが、うまく挙動しない場合もあるかもしれません。

・formタグのactionを設定すれば、あとはボタンを押すだけでアップロードされます。

・僕は、アップロード後の処理（バリデーション等）はLaravel側に任せております。他の方はVueでajaxを用いてアップロードしてました。