【Vue.js】Googleフォトっぽくドラッグアンドドロップで画像をアップロードする

最初は単なる画像のうp実装だったけど、同時にGoogleフォトの整理をしていると、いつのまにか(個人的に)天下のUI/UXに感化されていて、なんだかもう少し頑張ってみたくなったので、その時の一部を公開しようと思います。

今回のゴール

他の記事でもよく見かけるものとして、UIフレームワークにも出来合いのものがあったりするのですが、指定範囲内に画像をもっていくパターンのアレがありますね。

こんなの↓

でもやっぱりウィンドウ内ならどこでもアップロードのほうが使っていて心地よい。

こんなの↓

ということで、着地はあくまでそちらです。

開発環境は割愛しますが、言語はTypeScript on Vue.jsで、ブラウザはChrome以外未確認です。

今更だけどアイキャッチ適当すぎて笑えない。

全体像

先にソースコード載せておきます。

template

<template>
  <div class="photo-gallery" @dragenter.prevent="toggle(true)">
    <p>ここにホバーアクションでオーバーレイ出現</p>
    <img v-for="image in images" :src="image">
  </div>
  <transition name="fade">
    <div
      v-if="isVisible"
      class="overlay"
      @dragover.prevent="dragOver()"
      @dragleave.prevent="toggle(false)"
      @drop.prevent="upload($event)"
    >
      <p>ドラッグアンドドロップでアップロード</p>
    </div>
  </transition>
</template>

script

<script lang="ts">
import { Vue, Component, PropSync, Watch } from 'nuxt-property-decorator'

@Component
export default class PhotoGallery extends Vue {
  isVisible = false
  images = []

  toggle(isVisible: boolean) {
    this.isVisible = isVisible
  }

  dragOver() {
    console.log('dragover')
  }

  upload(event: any) {
    const imageFiles = event.dataTransfer.files
    for (var i = 0; i < imageFiles.length; i++) {
      this.images.push(window.URL.createObjectURL(imageFiles[i]))
    }
    this.isVisible = false
  }
}
</script>

style

<style scoped>
.overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 1;
}

.overlay > p {
  pointer-events: none;
}

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
</style>

コード量節約のため処理やスタイリングは必要最低限にしております。

また、JSFiddleでも可能な範囲で再現してみました。
JavaScriptで書いている方はこちらをどうぞ。

実装の勘所

アップロードエリアをふわっと出現

Vueならではですがtransitionを使います。

transitionはnameに設定した名前は名前+”-enter-active”、名前+”-leave-active”というような形で使えるようになるので、CSSにてアニメーションプロパティを設定します。

設定できるtransitionクラスについてはこちらです。

Drag & Drop系イベント

細かくすると非常に長くなるので、DnDのことがしっかり書かれた記事を載せておきます。ドキュメントを確認した後に読み物としてどうぞ。

 

特にDnDではイベント修飾子が重要になってくるので、こちらも要確認です。

バブリングで引き起こすフラッシュ問題

オーバーレイを敷いた後、文言や画像等の子要素をセットして画像をドラッグすると、子要素にホバリングした際オーバーレイにちらつきが発生します。

これは子要素を通過した一瞬にdragleave, dragenterイベントが発火するためです。

構造がシンプルな時は子要素にpointer-events: noneを加えてあげると解決します。

ドロップ時の複数画像アップロード

用途にもよりますが、画像アップロードは必ずしも単数とは限らないので、複数でも対応できるようにしておきます。

dropイベントでは発火時ドロップされた要素の情報を取得できます。

ソースコードのイベントハンドラに沿うと、e.dataTransfer.filesにアップロードされたファイル群が入っております。([File, File,]のようなファイルオブジェクトの配列です。)

なのでこれをそのままループで回してwindow.URL.createObjectURLで表示用に画像パスへと変換してます。

アップロード画像の追加・削除

今回書いたのは追加しかできず、アップロード済み画像は削除ができないようになっているので、これを実現するには、imagesにウォッチャーを置いたアプローチがピュアな関数との親和性が高く、適切かも知れません。

最後に

Web APIやイベント関連は奥が深い上に数もとてつもなく多いので、まだまだ学ぶことだらけです。