こんにちは、フロントエンドエンジニアのshimotsuです。

個人のプロジェクトとして、Nuxt.jsとGCPのmBaaSとして広く知られる「Firebase」を使って趣味である釣りの道具を管理するサービス「LINEDAY」を作りました。今回はその制作の背景や、ハマりポイントなどをトピックごとに紹介したいと思います。

今回作ったサービス「LINEDAY」について

釣りをするときには、「リール」と呼ばれる、釣り竿に取り付けて糸を巻き取る道具を使います。糸は数回使ったら新しいものに巻きかえる必要があるのですが、釣りに行く頻度が月1〜月に数回というレベルだと、最後にいつ糸を巻き替えたか分からなくなってしまう、という問題が出てきます。「LINEDAY」はそんな問題を解消するサービスです。完全に釣り人のためのサービスです。

機能

サービスの機能は以下のとおりで、とてもシンプルな内容です。

  • SNSアカウントでのログイン機能(ユーザを識別するため)
  • リールを登録する機能(名前+写真)
  • リールに糸を巻いた日を記録する機能
  • リールを使った日を記録する機能

技術構成

使った技術は以下のとおりです。

  • フロントエンド:Nuxt.js
  • データベース:Firebase Realtime Database
  • 認証:Firebase Authentication
  • ホスティング(+ ドメイン):Netlify

認証とデータベースという、このサービスに必要な機能はまるっとFirebaseに任せています。プランは現在のところ「Sparkプラン」に設定しており、料金は無料です。
今回Firebaseを使ったのは初めてだったのですが、Firebaseはドキュメントが豊富で特にすごく困ることなく最後までスムーズに作ることができました。

ホスティングも無料で使えるNetlifyを使っており、このサービス制作において実質払っているのはドメイン代(年間約800円)のみ。認証機能、DB作成、ホスティングまでをこなしてほぼ無料という(もちろんスケールしたら別ですが)、とてもコストの低い構成です。

それでは、トピックごとに解説していきます。
(※ Firebaseの基本的な機能については、公式ドキュメントが充実しているので今回は省きます。それ以外の機能について書いていきます。)

画像のプレビュー機能

今回、画像のプレビュー機能にはblueimp-load-imageというライブラリを使いました。スマホからExif情報を持った画像をアップロードすると、ブラウザによって意図しない向きにアップロードされることがあるので、プレビュー前にそれを補正するために使っています。

なお、今回はスマホのChromeとSafariのみ動けばそれでOKというスタンスで作ったので、向きの補正については以下のような感じで書きました。

loadImage.parseMetaData(file, (data) => {
        const options = {
          maxHeight: 500,
          maxWidth: 500,
          canvas: true
        }
        if (data.exif) {
          const userAgent = window.navigator.userAgent.toLowerCase()

          if (userAgent.includes('chrome')) {
            options.orientation = 1
          } else if (userAgent.includes('safari')) {
            options.orientation = 6
          } else {
            options.orientation = 1
          }
        }
      })

また、optionsというオブジェクトにmaxHeight/maxWidthというキーがあり、数値を設定していますが、これを指定することでmaxの値をもとにリサイズしてくれます。
最近のスマホで撮影した画像は大きいので、そのままアップロードしてしまうとすぐに通信量を圧迫してしまいます。ですのでリサイズがほぼ必須なのですが、blueimp-load-imageを使うとこのように簡単にリサイズできるので便利です。

ハマりポイント:inputタグのaccept属性の指定

画像のアップロードのために、今回はセオリー通りinputタグを使用しています。
そのなかでaccept属性を最初はこのように指定していました。.jpegと.jpg、.pngの拡張子だけを受け付ける設定です。

<input id="upload" @change="attachImg" type="file" accept=".jpeg, .jpg,  .png" />

ところが、ChromeとSafariでは普通に動くのですが、LINEのアプリ内ブラウザでなぜか動かない…、という事態に。なぜか原因が分からず四苦八苦したのですが、よく調べると、拡張子ではなくMIMEタイプ書かないと動かないということがわかりました。

参考記事:【WordPress + Contact Form7 + Android】AndroidのLINEのブラウザ(Webview)で「添付ファイル」ボタンが動かない(input type= “file”)

そこで、以下のように拡張子の指定に加え、MIMEタイプの指定を追記すると無事動くようになりました。

<input id="upload" @change="attachImg" type="file" accept="image/jpeg, image/png, .jpg, .jpeg, .png" />

デイピッカー「VueCtkDateTimePicker」による日付指定

サービスの機能上、日付を選択するUIが必要だったのですが、今回はVueで使えるvue-ctk-date-time-pickerというライブラリを使用しました。
とても実装が簡単で、それ専用のコンポーネントをimportして使うだけで、それっぽいモダンなデイピッカーのUIを実装できてしまうすぐれものです。

コンポーネントは以下のように記述するだけです。
Props APIが用意されているので、色やボタンの有無、オーバーレイの表示など、デイピッカーに必要な一通りのカスタムはおこなえる印象です。
選択された日付を変数 requestDate として双方向バインディングしています。

<Datetime
  id="DatePicker"
  v-model="requestDate"
  :format="'DD-MM-YYYY'"
  :formatted="'ll'"
  :overlay="false"
  :onlyDate="true"
  :noLabel="true"
  :auto-close="true"
  :no-button-now="true"
  :no-header="true"
  color="#142850"
  label="日付を選択してください"
  ></Datetime>

もちろん、細かいカスタムについては手が行き届かないケースもありますが(カレンダー上部の日付が漢数字になってたり)、サクッとデイピッカーを作りたい場合は有力な選択肢になるかと思います。

また、今回使い勝手の面でちょっとこだわった点として、特に「保存」とか「確定」ボタンを押さなくとも、カレンダーで選択した日付がリールの情報として保存されるようにしています(gifを参照ください)。

当初、【日付を選択】→【「保存」 or 「確定」ボタンを押したらDBに保存】というフローだったのですが、ボタンを押さないと保存できない仕組みがちょっと手間だなと……。

そこで、双方向バインディングしている日付を格納する変数 requestDate をウォッチし、その値が変更されたら、リールの情報をアップデートする関数を実行するようにしました。日付が選択された時点で保存されるので、「保存」ボタンもなくすことができました。

watch: {
    requestDate(val, oldval) {
      // リールのモーダルを開いているときのみ動作させる
      if (this.showReelModal) {
        // ※実際には、以下の処理は関数化してます
         const ref = firebase.database().ref('user')

         // Firebase内のDBをアップデート
         ref
          .child(this.user.uid)
          .child('reels')
          // 選択されたリールのkeyを取得
          .child(this.$store.state.reels[this.showReelNum].key)
          .update({
            linedDay: this.requestDate
          })
          // storeでリールの情報をフェッチし、stateに格納するアクションを実行
          .then(this.$store.dispatch('fetchReels', this.user.uid))
      }
    },

さいごに

というわけで、駆け足でちょっとした機能ごとに説明してみました。

このようにFirebaseの認証機能(Firebase Authentication)と、データベース(Firebase Realtime Database)を使うと、この程度のミニマルなサービスはすぐに作れるようになることがわかりました。

僕個人としても、mBaaSの世界の第一歩を踏み出したばかりなので、今後もどんどん個人的なプロジェクトに組み込んでいきたいと思います。

【告知】弊社のメンバーが執筆した書籍が発売されました!

弊社のメンバーが執筆した『初心者からちゃんとしたプロになる JavaScript基礎入門』が発売されました! JavaScriptの基礎からVue.jsまでをカバーしており、初学者から基本の復習をしたい方におすすめの1冊です。