7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Vuetify3/Nuxt3] ファイルアップロードボタンの実装

Last updated at Posted at 2022-05-21

はじめに

画面のデザイン的に、Vuetify3のデフォルトのアップロードボタンでなく、いかにもボタンボタンしてる アップロードボタンが作りたくなりました。Vuetify2での例はありましたが Vuetify3での例がなかったのでそのままコピペできず、ちょっと調べることになりました。

開発環境

  • Nuxt 3.0.0-rc.1
  • Vuetify 3.0.0-beta.1

ほしいイメージ

これじゃなくて
これがほしい

結果

過程をいっぱい書いても 興味ない方も居ると思うので出来上がったものをまず置いときます

1: ボタンがある

image.png

2: クリックでクルクルアニメーション+選択ダイアログ

image.png

3: 選択後ファイル名が出る + emitされる

image.png

-: ドラッグアンドドロップもできる

messageImage_1653136247393.jpg

FileSelectButton.vue
<template>
  <v-btn
      @dragover.prevent="isDragged = true"
      @dragleave.prevent="isDragged = false"
      @drop.prevent="onFileDropped"
      width="100%"
      height="100%"
      color="info"
      class="text-none"
      :variant="!isDragged ? 'contained' : 'outlined'"
      depressed
      @click="openFileSelect"
  >
      <v-progress-circular
          v-if="isSelecting"
          class="mx-12 my-6"
          indeterminate
          color="white"
      />
      <v-row v-if="!isSelecting" class="text-center" justify="center">
          <v-col class="mt-2 mb-n2" cols="10">
              <v-icon size="40">
                  {{ props.buttonIcon }}
              </v-icon>
          </v-col>
          <v-col class="text-truncate mb-2 mt-n2" cols="10">
              {{ displayText }}
          </v-col>
      </v-row>
  </v-btn>
  <input
      ref="uploader"
      class="d-none"
      type="file"
      :accept="props.accept"
      @change="onFileSelectChange"
  >
</template>

<script setup lang="ts">
import {
  mdiCloudUpload
} from '@mdi/js'

interface Props {
  buttonTitle?: string,
  buttonIcon?: string,
  accept?: string
}
const props = withDefaults(defineProps<Props>(), {
  buttonTitle: "ファイルを選択",
  buttonIcon: mdiCloudUpload,
  accept: "image/*"
})

interface Emits {
  (e: "update:File", value: File): void;
}
const emits = defineEmits<Emits>()

const isSelecting = ref<boolean>(false)
const isDragged = ref<boolean>(false)
const selectedFile = ref<File | null>(null)

const displayText = computed(() => {
  switch (true) {
    case isDragged.value:
      return "離すとアップロード"
    case selectedFile.value != null:
      return selectedFile.value!.name
    default:
      return props.buttonTitle
  }
})


// ボタンクリックでファイル選択を開く
const uploader = ref<HTMLInputElement>()
const openFileSelect= () => {
  isSelecting.value = true
  window.addEventListener('focus', () => {
    isSelecting.value = false
  }, { once: true })
  uploader.value?.click()
}

// ファイル選択確定時にEmit
const onFileSelectChange = (e: Event) => {
    const target = e.target as HTMLInputElement
    const files = target.files
    const file = files![0]
    selectedFile.value = file
    emits("update:File", file)
}

// D&D時にEmit
const onFileDropped = (e: DragEvent) => {
    isDragged.value = false
    if (!e) {
      return
    }

    if (!e.dataTransfer) {
      return
    }

    if (e.dataTransfer.files.length === 0) {
      return
    }
    const file = e.dataTransfer.files[0]
    selectedFile.value = file
    emits("update:File", file)
}
</script>

作ってみる

過程に興味ある人向け (様々な資料を参考にさせていただきました)

1: 既存のファイルアップロードボタンを見てみる

Googleで Vuetify file upload button と検索すると Vuetify2の例が出てくる

どうやらv-btnが押されたら refsを経由して input type="file" の要素をクリックするという
処理をするということがわかった(そんなことできるのか...)

2: Vue3 の refsの仕様を調べる

Vue2でrefsは使った覚えがあったが Vue3のrefsの使い方がよくわからなかったのでCompositionAPIの記事を参考にさせていただいた

そして型についてはなんやかんや困ったが
どうやらHTMLInputElement型を取ってくればいいということがわかった
Eventに関しては 正しい使い方がちょっとわからなかったが as で捻じ曲げて e.targetを HTMLInputElement として扱って とりあえずtypescript文法上は動くようにした

// ファイル選択確定時にEmit
const onFileSelectChange = (e: Event) => {
    const target = e.target as HTMLInputElement
    const files = target.files
    const file = files![0]
    selectedFile.value = file
    emits("update:File", file)
}

3: Vue3.2 Setupでの Props/Emitを調べる

Nuxt3 RCすげ~!って始めたので 実はVue3.2自体知らない、よってこれも調べた。とても参考にさせていただいた。

4: ドラッグアンドドロップに対応させる

できればドラッグアンドドロップに対応させたいよねってことでこちらも参考にさせて頂いた。こちらはほぼそのままで動かすことができた。

5: ドラッグアンドドロップ時にボタンをアウトラインにする

Vuetify2の v-btn は outlined をつけるとアウトラインになったが Vuetify3 では variant=outlined を指定するようになっていた。これに気づかず若干ハマった。

6: サイズ調整

そのままだと絶妙に残念なサイズをしていたので、親要素にサイズを任せることにして
width="100%"height="100%"をつけたら いい感じになった

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?