【Laravel】Vuejs、Inertia、Laravelでの画像アップロードの方法

laravelアイキャッチ JavaScript

こんにちは、かつコーチです。

今回は、Vuejs、Inertia、Laravelを使って開発しているときの、画像アップロードの方法について解説します。

Laravelで開発しているときに、結構ハマったところなので、備忘録として書いておきます。

方法はいろいろありますが、それぞれについて記載しておきます。

通常のアップロード方法

まずは、通常のファイル選択をしてのアップロード方法です。

const store = () => {
  form.post(route('tests.store'));
};

<input type="file" id="image"
  @input="form.test_photo_path = $event.target.files[0]"
  accept="image/*"
>

Inertiaの公式に記載している通り、@input=”form.<imageのパス = $event.target.files[0]” とすることで、コントローラー側へ送信できます。

これをコンポーネント化しておきましょう。

<BasicImageUpload
 label="店舗画像"
 placeholder="/no-image.png"
 inputId="test_photo_path"
  updateEvent="update:test_photo_path"
  @update:test_photo_path="handleFileInput"
/>

通常は、これで良いと思いますね。

Drag & DropになるとAPIが必要になりますが、

Drag & Dropってデスクトップでしか使えないものなので、

特に必要ないならこの通常の方法で良いと思います。

Drag & Dropでアップロードする方法

昨今ではDrag & Dropでアップロードする手法が増えて来ました。

Drag & Dropのときは通常時と少し扱いが異なります。

こちらもコンポーネントにしました。

コード全体

<script setup>
import { computed, ref, watch } from "vue";
import axios from "axios";
import CloudUpload from "vue-material-design-icons/CloudUpload.vue";

const props = defineProps({
  modelValue: File,
});

const emit = defineEmits(["update:modelValue"]);

const inputFile = ref(null);
const imageSrc = ref("");
const active = ref(false);
const dragCount = ref(0);

const imageStyle = computed(() => {
  return {
    backgroundImage: imageSrc.value ? `url(${imageSrc.value})` : "",
    backgroundColor: active.value ? "pink" : "",
    border: imageSrc.value ? "none" : "2px dashed #5a67d8",
    backgroundSize: "cover",
    backgroundPosition: "center",
    width: "100%",
    height: "100%",
    objectFit: imageSrc.value ? "cover" : "contain",
  };
});

const onDragEnter = (e) => {
  e.preventDefault();
  dragCount.value++;
  active.value = true;
};

const onDragLeave = (e) => {
  e.preventDefault();
  dragCount.value--;
  if (dragCount.value === 0) {
    active.value = false;
  }
};

const uploadImage = (e) => {
  const file = e.target.files[0] || e.dataTransfer.files[0];
  if (file) {
    imageSrc.value = URL.createObjectURL(file);
    emit("update:modelValue", file);
  }
};

const onDrop = (e) => {
  e.preventDefault();
  e.stopPropagation();
  inputFile.value.files = e.dataTransfer.files;
  uploadImage({ target: { files: e.dataTransfer.files } });
  active.value = false;
};

watch(
  () => props.modelValue,
  (newValue) => {
    if (newValue) {
      imageSrc.value = URL.createObjectURL(newValue);
    }
  }
);
</script>

<template>
  <div class="w-full flex justify-center">
    <label
      for="input-file"
      id="drop-area"
      class="w-80 h-80 p-8 text-center rounded-2xl cursor-pointer"
      @dragenter="onDragEnter"
      @dragleave="onDragLeave"
      @dragover.prevent
      @drop="onDrop"
    >
      <input
        type="file"
        accept="image/*"
        id="input-file"
        @change="uploadImage"
        ref="inputFile"
        class="hidden"
      />
      <div
        :style="imageStyle"
        class="w-full h-full rounded-2xl border-2 border-dashed border-indigo-500 bg-slate-50 hover:bg-slate-100 transition object-contain object-center focus:bg-slate-800"
      >
        <CloudUpload
          v-if="!imageSrc"
          class="text-indigo-500 mt-8 inline-block"
          size="80"
        />
        <p v-if="!imageSrc" class="text-base text-slate-700 mt-2">
          Drag and drop or click here <br />
          to upload image
        </p>
        <span v-if="!imageSrc" class="block text-sm text-slate-600 mt-2">
          Upload any images from desktop
        </span>
      </div>
    </label>
  </div>
</template>

このコンポーネントは、画像のアップロード機能を提供するための「ドラッグ&ドロップ」インターフェースを作成しています。

Vue.jsを用いて、画像ファイルをドロップまたはクリックして選択することで

アップロードする仕組みです。

コードを段階的に解説し、各部分がどのように機能するのかを説明します。

全体の流れ

このコンポーネントは、ユーザーが画像をドラッグ&ドロップまたはクリックしてアップロードできるようにします。画像が選択されると、その画像が表示され、modelValueというプロパティに新しい画像ファイルが渡されます。

Vue.jsの基本的な部分

  1. propsemit の定義:
    • props: 親コンポーネントから渡されたmodelValue(画像ファイル)を受け取ります。このファイルは後で画像のプレビューを表示するために使用されます。
    • emit: 親コンポーネントにmodelValueを更新するためのイベントを発行します。これにより、画像が変更されたときに親コンポーネントに通知します。
const props = defineProps({
  modelValue: File,
});

const emit = defineEmits(["update:modelValue"]);

リアクティブな変数:

  • inputFile: ファイル入力(<input type="file">)の参照。
  • imageSrc: 画像のプレビュー用のURLを格納します。選択された画像があればそのURLが格納され、なければ空のままです。
  • active: ドラッグオーバー中かどうかを追跡するフラグです。ドラッグされてきた際にスタイルが変更されます。
  • dragCount: ドラッグイベントが発生した回数を追跡します。これにより、複数のドラッグイベントの発生を管理できます。
const inputFile = ref(null);
const imageSrc = ref("");
const active = ref(false);
const dragCount = ref(0);

スタイルの動的設定

imageStyleは、画像がアップロードされたかどうか、またはドラッグ中かどうかに応じてスタイルを動的に変更するために使用されます。

const imageStyle = computed(() => {
  return {
    backgroundImage: imageSrc.value ? `url(${imageSrc.value})` : "",
    backgroundColor: active.value ? "pink" : "",
    border: imageSrc.value ? "none" : "2px dashed #5a67d8",
    backgroundSize: "cover",
    backgroundPosition: "center",
    width: "100%",
    height: "100%",
    objectFit: imageSrc.value ? "cover" : "contain",
  };
});
  • 背景画像: imageSrcが設定されていればその画像を背景として表示し、そうでなければ空の状態になります。
  • ドラッグ中の背景色: activetrueのときにピンク色の背景が適用されます。
  • ボーダー: 画像がある場合はボーダーを非表示にし、画像がない場合は青色の点線ボーダーを表示します。

ドラッグイベントのハンドリング

以下のイベントでドラッグ&ドロップの動作を管理します。

  1. onDragEnter:
    • ドラッグが要素に入ったときに呼ばれます。
    • dragCountをインクリメントし、activetrueに設定して、ドロップエリアのスタイルを変えます。
const onDragEnter = (e) => {
  e.preventDefault();
  dragCount.value++;
  active.value = true;
};

onDragLeave:

  • ドラッグが要素から出たときに呼ばれます。
  • dragCountをデクリメントし、最後のドラッグイベントが終了した時点でactivefalseに戻します。
const onDragLeave = (e) => {
  e.preventDefault();
  dragCount.value--;
  if (dragCount.value === 0) {
    active.value = false;
  }
};

onDrop:

  • ドロップしたときに呼ばれるイベントです。
  • e.preventDefault()e.stopPropagation()でデフォルトの動作(ファイルが別の場所にドロップされる)を防ぎます。
  • ドロップされたファイルをinputFileに設定し、uploadImageメソッドを呼び出して画像をアップロードします。
const onDrop = (e) => {
  e.preventDefault();
  e.stopPropagation();
  inputFile.value.files = e.dataTransfer.files;
  uploadImage({ target: { files: e.dataTransfer.files } });
  active.value = false;
};

画像アップロード

uploadImageメソッドは、ファイルが選択されると呼ばれます。ファイルが選択されると、そのファイルをURL.createObjectURLを使って一時的なURLを生成し、imageSrcに設定します。その後、emitを使って親コンポーネントに新しい画像を通知します。

const uploadImage = (e) => {
  const file = e.target.files[0] || e.dataTransfer.files[0];
  if (file) {
    imageSrc.value = URL.createObjectURL(file);
    emit("update:modelValue", file);
  }
};

watchによる監視

watchを使用して、modelValueが変更されるたびに画像のURLを更新します。これにより、親コンポーネントで画像が変更されると、即座に画像が表示されます。

watch(
  () => props.modelValue,
  (newValue) => {
    if (newValue) {
      imageSrc.value = URL.createObjectURL(newValue);
    }
  }
);

HTML部分

  1. <label>要素:
    • ドラッグ&ドロップエリアをラップする<label>要素です。dragenter, dragleave, dropイベントをリスンして、ユーザーがドラッグまたはドロップを行ったときに対応します。
  2. ファイル入力:
    • <input type="file">は、ユーザーがファイルを手動で選択するために使用されます。この入力フィールドは隠れており、labelをクリックすることでファイル選択ダイアログが表示されます。
  3. 画像のプレビュー:
    • 画像が選択されていない場合、CloudUploadアイコンとテキストが表示されます。画像が選択された場合、その画像のプレビューが表示されます。

まとめ

このコンポーネントは、Vue.jsで画像のドラッグ&ドロップを効率的に処理する方法を提供しています。ドラッグ、ドロップ、ファイル選択、プレビュー表示などのユーザーインタラクションを管理し、選択された画像を親コンポーネントに通知します。フロントエンドでこのような機能を実装することで、ユーザーに直感的でスムーズな体験を提供できます。

タイトルとURLをコピーしました