グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



【Vue.js】ドラッグ&ドロップでアップロードした画像をプレビュー表示してみる

最近、 input type=”file” を使った画像をアップロードと、アップロードした画像のプレビューを実装する機会がありました。

そこで今後のために、 input type=”file” だけでなく、ドラッグ&ドロップを使った画像アップロードとプレビューを実装してみようと思いこの記事を書きました。

前提

1.htmlを用意

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Practice Vue.js ImagePreview and DnD</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>
<body>

    <div id="app">
        <div class="preview_zone">
            <img src="" alt="ここにプレビューが表示されます">
        </div>
        <div class="upload_zone">
            <input type="file" class="input_file">
            <div class="drop_zone">
                ここにファイルをドラッグ&ドロップ
            </div>
        </div>
    </div>

    <script>
    </script>

</body>
<style>

    html, body {
        height: 100%;
    }

    body {
        display: flex;
        justify-content: center;
        align-items: center;
    }

    #app {
        display: flex;
        align-items: center;
    }

    .drop_zone {
        border: 3px solid lightgray;
        border-radius: 20px;
        width: 300px;
        height: 150px;
        display: flex;
        justify-content: center;
        align-items: center;
        color: lightgray;
        font-weight: bold;
        margin-top: 20px;
    }

    .preview_zone {
        border: 2px solid black;
        width: 400px;
        height: 500px;
        margin-right: 20px;
    }
  
    img {
        object-fit: contain;
        width: 100%;
        height: 100%;
    }

</style>
</html>

7行目でVue.jsをCDN経由で読み込んでいます。筆者が使用したバージョンが2.6.14だったのでこれで固定しています。

プレビューを表示する枠、 input type=”file” 、ドラッグ&ドロップする枠を下記画像のように表示できました。

2.input type=”file”でアップロード画像ファイルをプレビュー表示する

まずはよくある、 input type=”file” を使って画像プレビューを表示してみます。

-- a.html      2022-11-08 11:56:24.888679200 +0900
+++ b.html      2022-11-08 11:55:19.768679200 +0900
@@ -10,10 +10,10 @@

     <div id="app">
         <div class="preview_zone">
-            <img src="" alt="ここにプレビューが表示されます">
+            <img :src="url" alt="ここにプレビューが表示されます">
         </div>
         <div class="upload_zone">
-            <input type="file" class="input_file">
+            <input type="file" class="input_file" ref="preview" @change="previewImage">
             <div class="drop_zone">
                 ここにファイルをドラッグ&ドロップ
             </div>
@@ -21,6 +21,19 @@
     </div>

     <script>
+        const app = new Vue({
+            el: '#app',
+            data: {
+                url: '',
+            },
+            methods: {
+                previewImage() {
+                    let image = this.$refs.preview.files[0];
+                    this.url = URL.createObjectURL(image);
+                }
+            }
+
+        })
     </script>

 </body>

2-1. refを使ってinput要素にアクセス

Vue.jsのref属性を利用することでinput要素で選択したファイル情報を取得できます。
htmlには  ref=”任意の値” と記述します。(12行目)

<input type="file" class="input_file" ref="preview" @change="previewImage">

Vue.jsからは、 this.$refs.任意の値.files でファイル情報を取得できます。(27行目)
今回は任意の値を”preview”にしているので、これでアクセス可能です。

let image = this.$refs.preview.files[0];

2-2. アクセスしたファイル情報の取得

changeイベントを利用します。メソッド名は previewImage にします。(12行目)

<input type="file" class="input_file" ref="preview" @change="previewImage">

Vue.jsに previewImage メソッドを追加します。(26行目付近)

methods: {
    previewImage() {

2-3. プレビュー表示のための一時的なURLを作成する

URL.createObjectURL()を使って一時的なURLを作成します。

URL.createObjectURL()はブラウザ閉をじるか、URL.revokeObjectURL()を呼び出すかまでメモリを解放しないので、使う際はこのことを頭の片隅に入れておいた方が良さそうです。

のちにimgタグのsrc属性にこのURLを指定することで、画像プレビューを表示できるようになります。
Vue.jsに作成したURLを保存するプロパティを追加。(23行目付近)

data: {
    url: '',
},

そのプロパティに作成したURLをセットします。(28行目)

this.url = URL.createObjectURL(image);

2-4. プレビュー表示

imgタグのsrc属性にこのURLを指定します。

<img :src="url" alt="ここにプレビューが表示されます">

動作確認

「ファイルを選択→ファイルを選択→プレビュー表示」のようにすると下記画像のように無事表示されました。

3.ドラッグ&ドロップを実装

--- b.html      2022-11-08 11:55:19.768679200 +0900
+++ c.html      2022-11-08 13:45:22.738679200 +0900
@@ -14,7 +14,9 @@
         </div>
         <div class="upload_zone">
             <input type="file" class="input_file" ref="preview" @change="previewImage">
-            <div class="drop_zone">
+            <div class="drop_zone"
+                @dragover.prevent
+                @drop.prevent="dropImage">
                 ここにファイルをドラッグ&ドロップ
             </div>
         </div>
@@ -30,7 +32,10 @@
                 previewImage() {
                     let image = this.$refs.preview.files[0];
                     this.url = URL.createObjectURL(image);
-                }
+                },
+                dropImage() {
+                    console.log('Dropped');
+                },
             }

         })

3-1. dragoverイベント

ドラッグした画像ファイルが「ここにファイルをドラッグ&ドロップ」エリア上にある時に、絶えず dragover  イベントが発生します。今回はこのタイミングでのイベントを使わないので、イベントが発生しないように、Event.preventDefaultの設定をします。Vue.jsではイベントに .prevent を付けることで設定します。(9行目)

@dragover.prevent

3-2. dropイベント

ドラッグした画像ファイルが「ここにファイルをドラッグ&ドロップ」エリア上にドロップした際にファイルの情報を取得したいので、dropイベントを利用し、 dropImage 関数を実行させます。

注意点として、ブラウザは、「ファイルをブラウザにドロップするとファイルの内容を画面に表示する」、というデフォルトの動きを持っています。したがってその動きをさせないために、Event.preventDefaultの設定をします。(10行目)

@drop.prevent="dropImage"

動作確認

動作を確認するために、 dropImage 関数ではconsoleにdroppedの文字を出力してみます。(21行目)
エクスプローラーから、「ここにファイルをドラッグ&ドロップ」エリアにファイルをドロップすると、下記画像のようにconsoleにdroppedの文字が出力されると思います。

3.5「ここにファイルをドラッグ&ドロップ」エリア上をドラッグしている時の装飾

--- c.html      2022-11-08 13:45:22.738679200 +0900
+++ d.html      2022-11-08 13:58:47.088679200 +0900
@@ -16,7 +16,10 @@
             <input type="file" class="input_file" ref="preview" @change="previewImage">
             <div class="drop_zone"
                 @dragover.prevent
-                @drop.prevent="dropImage">
+                @drop.prevent="dropImage"
+                @dragenter="dragEnter"
+                @dragleave="dragLeave"
+                :class="{enter: isEnter}">
                 ここにファイルをドラッグ&ドロップ
             </div>
         </div>
@@ -27,12 +30,19 @@
             el: '#app',
             data: {
                 url: '',
+                isEnter: false,
             },
             methods: {
                 previewImage() {
                     let image = this.$refs.preview.files[0];
                     this.url = URL.createObjectURL(image);
                 },
+                dragEnter() {
+                    this.isEnter = true;
+                },
+                dragLeave() {
+                    this.isEnter = false;
+                },
                 dropImage() {
                     console.log('Dropped');
                 },
@@ -85,5 +95,10 @@
         height: 100%;
     }

+    .enter {
+        border: 3px dotted lightblue;
+        color: lightblue;
+    }
+
 </style>
 </html>

3.5-1. classバインド

Vue.jsに isEnter プロパティを追加。(19行目付近)
「ここにファイルをドラッグ&ドロップ」エリアに侵入していない時はfalseとして、「ここにファイルをドラッグ&ドロップ」エリアに侵入している時はtrueにします。
初期値は「ここにファイルをドラッグ&ドロップ」エリアに侵入していない状態から始まるので、falseです。

isEnter: false,

styleタグ内に「ここにファイルをドラッグ&ドロップ」エリアに侵入している時に適応させたいcssを書きます。(39行目付近)

.enter {
    border: 3px dotted lightblue;
    color: lightblue;
}

classバインドさせます。(11行目)
 isEnter プロパティがtrueだとclassタグにenterが追加されて .enter で指定したcssが適応されます。

:class="{enter: isEnter}"

3.5-2. dragenterイベント

ドラッグした画像ファイルが「ここにファイルをドラッグ&ドロップ」エリアに侵入してきた時に、dragenterイベントが発生します。このタイミングで先ほど記載した isEnter プロパティの値をtrueに変更する dragEnter 関数を実行します。(26行目)

dragEnter() {
    this.isEnter = true;
},

3.5-3. dragleaveイベント

ドラッグした画像ファイルが「ここにファイルをドラッグ&ドロップ」エリア上から脱出した時に、dragleaveイベントが発生します。このタイミングで先ほど記載した isEnter プロパティの値をfalseに変更する dragLeave 関数を実行します。(29行目)

dragLeave() {
    this.isEnter = false;
},

動作確認

エクスプローラーから画像ファイルを「ここにファイルをドラッグ&ドロップ」エリア上にドラッグすると、下記画像のように装飾が変わりました。

4.ドラッグ&ドロップでアップロードした画像ファイルのプレビューを表示

--- d.html      2022-11-08 13:58:47.088679200 +0900
+++ e.html      2022-11-08 14:02:23.748679200 +0900
@@ -13,13 +13,14 @@
             <img :src="url" alt="ここにプレビューが表示されます">
         </div>
         <div class="upload_zone">
-            <input type="file" class="input_file" ref="preview" @change="previewImage">
+            <input type="file" class="input_file" ref="preview" @change="inputImage">
             <div class="drop_zone"
                 @dragover.prevent
                 @drop.prevent="dropImage"
                 @dragenter="dragEnter"
                 @dragleave="dragLeave"
-                :class="{enter: isEnter}">
+                :class="{enter: isEnter}"
+                >
                 ここにファイルをドラッグ&ドロップ
             </div>
         </div>
@@ -33,8 +34,11 @@
                 isEnter: false,
             },
             methods: {
-                previewImage() {
+                inputImage() {
                     let image = this.$refs.preview.files[0];
+                    this.previewImage(image);
+                },
+                previewImage(image) {
                     this.url = URL.createObjectURL(image);
                 },
                 dragEnter() {
@@ -44,7 +48,9 @@
                     this.isEnter = false;
                 },
                 dropImage() {
-                    console.log('Droped');
+                    let images = event.dataTransfer.files;
+                    this.previewImage(images[0]);
+                    this.isEnter = false;
                 },
             }

4-1. プレビュー表示部分のコードを切り出す

先ほど実装した previewImage 関数は input type=”file” での画像アップロード、ドラッグ&ドロップでの画像アップロード、の両方で使用するので切り出します。
そうして、 previewImage 関数はurlプロパティに一時的に作成したURLをセットするだけの関数にします。(29行目)

previewImage(image) {
    this.url = URL.createObjectURL(image);
},

新たに inputImage 関数を作成して、(25行目)

inputImage() {
    let image = this.$refs.preview.files[0];
    this.previewImage(image);
},

changeイベントの値を inputImage に変更します。(8行目)

<input type="file" class="input_file" ref="preview" @change="inputImage">

4-2. dropイベントで取得した画像ファイルの情報を扱う

先ほど実装した dropImage 関数に、ドロップした画像ファイルの情報を変数に格納するよう記述します。(38行目)
ファイル情報の取得には、イベントのDataTransfer.filesを利用します。

let images = event.dataTransfer.files;
 images 変数にfilelistsオブジェクトとして格納されるので、1つ目の要素を previewImage 関数に渡します。(39行目)

this.previewImage(images[0]);

これで、画像ファイルをドラッグ&ドロップすると無事プレビュー表示ができます。

最後に、ドロップした後に「ここにファイルをドラッグ&ドロップ」エリアの装飾が元に戻るように、 isEnter プロパティをfalseに戻します。

動作確認

エクスプローラーから画像ファイルを「ここにファイルをドラッグ&ドロップ」エリア上にドラッグすると、下記画像のように装飾が変わり、ドロップすると、プレビューが表示されます。また、 input type=”file” 経由でも問題なくプレビューが表示されます。

おわりに

今回は input type=”file” とドラッグ&ドロップを使って、アップロードした画像をプレビュー表示する方法についてまとめてみましたが、フロントエンドの経験が乏しい筆者でも、比較的容易に実装ができました。

バックエンドを触る回数が多い分、たまに触るフロントエンドは結構楽しいので、今後もこのような記事を書いていきたいです。

この記事を書いた人

KJG
KJGソリューション事業部 システムエンジニア
大学4年時春に文系就職を辞め、エンジニアになることを決意し、独学でRuby、Ruby on Railsを学習。
約1年間の独学期間を経てアーティスへWebエンジニアとして入社。現在はWebエンジニアとして、主にシステムの開発・運用に従事している。
抽象的なもの、複雑なものを言語化して文章にするのが好きで得意。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします