Firebase③ Cloud Storage

はじめに

前回の記事に引き続き、Cloud Storageを利用して、ブログにサムネイル画像を設定できるようにします。

完成イメージはこのようになります。

スクリーンショット 20200426 21.26.59.png

前提条件

Vue C LIを用いた開発環境構築を行い、単一ファイルコンポーネントを用いています。また、UIライブラリとしてVuetifyを使用しています。

また、Cloud Storageのパスの構成は次のようになっています。

articles - articleId - files

記事に使われている画像の一覧を取得する

それでは早速コーディングをしていきましょう。

Cloud Storageに関する機能は、src/plugins/storage.jsからimportするのでした。

import firebase from '@/plugins/firebase'

export const storage = firebase.storage()

スクリプト部分は次のようになっています。

import { storage } from '@/plugins/storage'

export default {
  name: 'thumbnail-setting-dialog',
  props: {
    article: {
      type: Object,
      required: true
    },
  },
  data() {
    return {
      thumbnail: this.article.thumbnail,
      loading: true,
      error: false,
      dialog: false,
      images: [],
      selectedImage: '',
      fileLoading: 0,
    }
  },
  async created() {
    try {
      const storageRef = await storage.ref(`articles/${this.article.id}`)
      const res = await storageRef.listAll()
      res.items.forEach(itemRef => {
        itemRef.getDownloadURL().then(url => {
          this.images.push(url)
        })
      })
    } catch(e) {
      this.error = true
      console.log(e)
    } finally {
      this.loading = false
    }
  },

ファイルを取得する機能は、createdメソッドで発動します。createdメソッドはVue.jsのライフサイクルの一つです。インスタンスが生成されたときにフックされるため、APIの通信など一番初めに処理を実行したいときに呼び出します。

ストレージの参照を作成する

まずはじめに、ファイルへアクセスするための参照を作成します。Cloud Storageは、普段使い慣れているファイルシステムと同じように、階層構造になっています。 storage.ref()メソッドを使用して、現在の記事の参照まで移動しましょう。 そのために、次のように参照を作成しました。前提条件のファイル構成を参考にしてください。

storage.ref(`articles/${this.article.id}`)

this.article.idpropsで親から受け取ったデータになります。予想通り、現在編集している記事のIDが入っているので、これで正しく参照を作成することができます。

ディレクトリのファイル一覧を取得する

参照を作成できたので、ディレクトリの中に存在するすべてのファイルを取得しましょう。 listAll()メソッドで取得することができます。

const res = await storageRef.listAll()

取得に成功したファイル一覧は、forEachを利用して処理します。 個々のファイルの情報からは、ファイルをアップロードしたときと同じようにgetDownloadURL()メソッドから画像のURLを取得することができます。

取得したURLはdataプロパティのimages配列に格納しておきます。

res.items.forEach(itemRef => {
   itemRef.getDownloadURL().then(url => {
      this.images.push(url)
    })
  })

取得した画像一覧を表示する

ここまでの処理の中でエラーがなければ、記事に使用されている画像のURLの一覧が取得できたはずです。 このままではなにも表示されないので、画像を表示するHTML部分を実装しましょう。 全体像は次のようになっています。

<v-row>
  <v-col cols=12>
    <v-card>
      <.div v-if="loading">
        画像データの取得中...
        <v-progress-circular indeterminate color="red"></v-progress-circular>
      </div>
      <.div v-else-if="images.length === 0">
        この記事に画像は使われていません。
      </div>
      <v-container fluid v-else>
        <v-row>
          <v-col
            v-for="(image, index) in images"
            :key="index"
            :index="index"
            class="d-flex child-flex"
            cols="4"
          >
            <v-card flat tile class="d-flex">
              <v-img
                :id="index"
                :src="image"
                aspect-ratio="1"
                :class="{ selected: isSelected(index) }"
                @click="onClick"
              >
                <template v-slot:placeholder>
                  <v-row
                    class="fill-height ma-0"
                    align="center"
                    justify="center"
                  >
                    <v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
                  </v-row>
                </template>
              </v-img>
            </v-card>
          </v-col>
        </v-row>
      </v-container>
    </v-card>
  </v-col>
</v-row>

一番はじめのv-ifディレクティブにより、ローディング中にはプログレスサークルが表示されます。 もし、一つも画像が記事内で使用されていないのであれば、その旨を表示してあげるのが丁寧でしょう。

<.div v-if="loading">
  画像データの取得中...
  <v-progress-circular indeterminate color="red"></v-progress-circular>
</div>
<.div v-else-if="images.length === 0">
    この記事に画像は使われていません。
</div>

画像が表示できるのなら、v-forディレクティブによりすべての画像を表示させましょう。 その際に、Vuetifyのグリッドシステムを利用します。 v-containerの中は、12個のカラムに分割されるので、v-colcolsに4を指定すれば、4 + 4 + 4ということで、画像が3つづつに分割されます。

<v-container fluid v-else>
  <v-row>
    <v-col
      v-for="(image, index) in images"
      :key="index"
      :index="index"
      class="d-flex child-flex"
      cols="4"
    >
      <v-card flat tile class="d-flex">
        <v-img
          :id="index"
          :src="image"
          aspect-ratio="1"
          :class="{ selected: isSelected(index) }"
          @click="onClick"
        >
          <template v-slot:placeholder>
            <v-row
              class="fill-height ma-0"
              align="center"
              justify="center"
            >
              <v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
            </v-row>
          </template>
        </v-img>
      </v-card>
    </v-col>
  </v-row>
</v-container>

グリッドシステムによる画像の配置は公式ドキュメントを参考にしました。

画像URLをv-imgsrcプロップスに渡せば画像を表示してくれます。 v-imgコンポーネントは優れもので、特に気にせずとも画像を遅延読み込みしてくれます。

選択された画像をサムネイルに設定する

画像一覧が表示されたので、画像が選択されたらサムネイルに設定されるようにしましょう。 v-img@clickイベントハンドラを用いてクリックイベントを購読しましょう。

onClick(e) {
  this.selectedImage = +e.target.parentElement.id
  this.thumbnail = this.images[e.target.parentElement.id]
},

v-imgには配列のインデックスをIDとして付与しているので、イベント引数から取得します。(v-imgコンポーネントを利用している都合上、画像自体にIDが不要されていないので、その親要素から取得しています。)

e.terget.parantElement.idの前についている奇妙な+記号は、文字列を数値へ変換するためのものです。 算術演算子の単項プラスはオペランドを評価する際に数値へ変換するので、値を数値へ変換する際に利用されます。 算術演算子 - JavaScript | MDN

後はインデックスから配列の中の選択された画像のURLを取得することができるので、これをthumbnailに設定すればOKです。

選択された画像を強調する

最後に、画像一覧から画像が選択された場合わかりやすいように黄色い枠で囲むようにしてみましょう。こういう感じです。

スクリーンショット 20200426 23.34.47.png

スクリーンショット 20200426 23.35.54.png

ボーダースタイルを定義する

画像を枠で囲むには、borderスタイルを適用します。次のようなCSSを書きます。

<style scoped>
.selected {
  border: medium solid #FFEB3B
}
</style>

このスタイルを、現在選択されている画像に適用すればいいわけです。

そのための機能がVue.jsには用意されています。クラスとスタイルのバインディング

:class(v-bind:class)の省略構文にオブジェクトを渡すと、クラスを動的に切り替えることができます。今回の例をもう一度上げましょう。

<v-img
    :id="index"
    :src="image"
    aspect-ratio="1"
    :class="{ selected: isSelected(index) }"
    @click="onClick"
>

オブジェクトのキーには適用させたいクラス名を渡し、プロパティデータには真偽値を渡します。つまり、isSelected(index)trueを返した場合には、selectedクラスが適用され先程定義されたスタイルが適用されます。

isSelectedは算出プロパティとして定義されています。

computed: {
   isSelected() {
     return index => this.selectedImage === index
  },
},

算出プロパティで引数を受け取るために、関数をreturnしています。 this.selectedImageには画像がクリックされたときにクリックされた画像のindexが入っているのでした。

算出プロパティは依存する値(今回はthis.selectedImage)が変更されるたびに新たな値を返すので、他の画像がクリックされるたび返す値が変わることになります。

これで、選択された画像にスタイルを適用させて強調させることができました。 この例で見たとおり、クラスバインディングは算出プロパティと組み合わせることで強力な効果を発揮します。

おわりに

あれ、なんだかCloud Storageの説明をしていたはずなのにVue.jsの説明のほうが多かった気がします。🤔

来週はついに、FireStoreを取り上げる予定です。かなり長くなりそうなので一週じゃ終わらない気がしますが。

この記事をシェアする
Hatena

関連記事