Nuxt.js + Firebase + fabric.jsでイラスト付きメッセージカードを贈れるサービスを作った

去年あたりからWebアプリの構成としてよく聞くようになったNuxt.jsとFirebase。

今回はこの構成と、canvasを簡単に扱えるfabric.jsを使用してwebサービスを作ったので 使用したライブラリやサービスを紹介したいと思います。

## つくったもの

**「cards」** https://cards.hauer.jp/

言葉やイラストを描いてメッセージカードをつくり、web上で贈ることができるサービスです。1分ほどでつくれます。

カード表面でデザインテンプレートを選んだあと、裏面がcanvasになっているので手書きで自由に文字やイラストを書いたり、textboxを使って文字を打ったり絵文字を挿入することができます。

LINEやメールの言葉だけじゃ素っ気ないし手紙を送るほどでは。。。といったときに使えるサービスがあればと思い作りました。

### デザインを選ぶ

目的に合わせてカード表面のテンプレートを選びます。

![design.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248843/378e0854-5876-8761-8634-4ac2d11ef32c.gif)

### 言葉やイラストを描く

裏面がcanvasになっていて自由にイラストを書いたり文字や絵文字を打てます。

![message.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248843/0b6de04b-c129-6d70-4db7-4b5c50be6636.gif)

### 送る

URLが生成されるのでカードを贈りたい相手に共有します。

![send.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248843/aec461ba-cc88-6ead-5941-498119b67f84.gif)



サンプルのメッセージカードです
[https://cards.hauer.jp/card/?id=3tbtRESzsB3k3gbUw6re](https://cards.hauer.jp/card/?id=3tbtRESzsB3k3gbUw6re)


## 構成

タイトルのとおり、Nuxt.js + Firebase で作りました。

すでにQiitaでは沢山の人がこの構成で作成していますが、やっぱり楽です。

### [Nuxt.js](https://ja.nuxtjs.org/)

Vue.jsのアプリケーションフレームワークです。今年に入ってからは動的なWebサイトも静的なWebサイトもNuxt.jsにお世話になりっぱなしです。

今回は静的ページとして書き出し、FirebaseのHostingにデプロイしてます。

またそこまで複雑なアプリケーションではないのですが、勉強も兼ねてVuexパターンで作成しました。

### [Firebase](https://firebase.google.com)

Googleが提供するmBaaS。

HostingやDatabase、Authenticationなどアプリケーションを作成するための機能を網羅していながら、スケールするまでは無料なので個人開発やプロトタイプの作成に向いています。

「[cards]( https://cards.hauer.jp/)」ではHostingとDatabaseを使用しています。

### [Fabric.js](http://fabricjs.com/)

HTML5 canvasを使いやすくするライブラリです。カードにお絵かき機能を実装するためにFabric.jsを使用しました。他にもcanvasを扱うライブラリとして[Create.js](https://createjs.com/)や[Paper.js](http://paperjs.org/)などがありますが、今回のアプリケーションは、

1. 文字の入力とドローイングに必要な機能を備えている
2. JSONでエクスポート、インポートができる
3. リッチな3Dオブジェクトや図表などは扱わない

という点からFabric.jsを選びました。[こちら](https://github.com/fabricjs/fabric.js/wiki/When-to-use-Fabric)にFabric.jsが得意なこと、苦手なことがまとまっています。

概ね満足していますが、スマホの操作感はもう少し改善できそうなので試行錯誤してみます。

### 他に使ったライブラリやサービス

#### [google-gtag](https://github.com/nuxt-community/google-gtag)

@nuxtjsが公式が提供しているgoogle Analytics用のgtagを埋め込むライブラリ。

`yarn add @nuxtjs/google-gtag`でインストールした後、nuxt.config.jsで設定。

```javascript:nuxt.config.js
modules: [
	[
		'@nuxtjs/google-gtag',
		{
			id: 'UA-XXXXXX-XX' //基本はこれだけでOK。デバッグや複数アカウント設定を行いたい場合はオプションで可能
		}
	]
]
```

これだけで全てのページでトラッキングができます。さらにコンポーネントのmethods内でeventコマンドを利用してデータを送信することが可能です。

```javascript
this.$gtag('event', 'your_event', { /* track something awesome */})
```

#### [vue-clipboard2](https://github.com/Inndy/vue-clipboard2)

任意のテキストをコピーすることができます。

`yarn add vue-clipboard2`でインストールした後、pluginsフォルダ以下にclipboard.jsファイルを作り初期読み込み。

```javascript:plugin/clipboard.js
import Vue from 'vue'
import VueClipboard from 'vue-clipboard2'

Vue.use(VueClipboard)
```

nuxt.config.jsでpluginを読み込み。

```javascript:nuxt.config.js
plugins: ['@/plugins/clipboard'],
```

これでv-clipboardディレクティブが使用できるようになります。

「[cards](https://cards.hauer.jp/)」では生成したカードのURLをコピーするボタンに使用しています。

```vue:send.vue
<Button
  v-clipboard:copy="cardUrl"
  v-clipboard:success="successCopyUrl"
  v-clipboard:error="errorCopyUrl"
  :class="{ black: isCopy }"
  >{{ isCopy ? 'copied!' : 'copy URL' }}</Button>
```

cardUrlがコピー対象で、ボタンコンポーネントをクリックしコピーが成功するとsuccessCopyUrl メソッド、エラーだとerrorCopyUrlメソッドが発火します。

#### [Animate.css](https://daneden.github.io/animate.css/)

CSSアニメーションを簡単に使えるライブラリです。

基本的には主にカード表面のデザインでアイコンをクリックした際のアニメーションに使用しています。一部アニメーションをカスタムしています。

#### [iconmonstr](https://iconmonstr.com/)

![スクリーンショット 2019-05-10 17.09.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248843/cdd73937-b783-4624-f534-4975f7807aba.png)


ちょっと遊び心があるアイコン素材を配布しているサイト。今回はほとんどのこちらのアイコンを使用させていただきました。完全に無料で様々な形式でダウンロードできるのが便利。

#### [Unsplash](https://unsplash.com/)

![スクリーンショット 2019-05-10 17.10.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248843/7c757b02-ed08-03a5-7a67-b7293b5f9e1e.png)


デザインテンプレートの画像はこちらのサイトを使用させていただきました。きれいめな画像多め。

## 制作時のポイント

詳しくは別記事で公開しようと思いますが2点だけ

### Vuexによるカードの状態管理とアニメーション

layouts/default.vueで<nuxt />とカードコンポーネント(<Card />)は切り離しているのでページ遷移によってカードコンポーネントは再レンダリングされません。Vuexで状態をみてカードコンポーネントが拡大縮小したり、フリップしたりします。

ヘッダー(<Header />)も同様でVuexで管理している状態によって移動したりサイズが変わります。

```vue:layouts/default.vue
<template>
  <div class="container">
    <Header />
    <Card />
    <nuxt />
  </div>
</template>
<script>
import Header from '~/components/Header.vue'
import Card from '~/components/Card.vue'

export default {
  components: {
    Header,
    Card
  }
}
</script>
<style scoped lang="scss">
.container {
  width: 100%;
  height: 100vh;
  margin: 0 auto;
  overflow-x: hidden;
}
</style>
```



colissさんの記事「[Vue.jsとNuxt.jsを使用して、Webページのページ遷移に気持ちいいアニメーションを与えるチュートリアル](https://coliss.com/articles/build-websites/operation/javascript/native-like-animations-for-page-transitions.html)」が詳しいです。

### Fabric.jsとVuexの組み合わせ

基本的にはcanvasの状態はStoreで管理してます。Fabric.jsのイベントトリガーに関してはmounted内に書いて都度dispatchしています。

```vue:Card.vue
<template>
  <div class="back-container">
    <canvas id="canvas" :width="width" :height="height" />
    <CardBackground :type="card.template.bg" />
  </div>
</template>
<script>
  // ...
  mounted() {
  this.$store.dispatch('canvas/init')
  this.canvas.data.on({
    'path:created': () => {
      this.$store.dispatch('canvas/change')
    },
    'object:modified': () => {
      this.$store.dispatch('canvas/change')
    },
    'text:editing:entered': e => {
      if (e.target.type === 'textbox') {
          if (e.target.text === 'Tap and Type.') {
            this.$store.dispatch('canvas/enterTextBox')
          }
      }
    }
  })
</script>

```

## 今後やりたいこと

### テンプレートデータをFireStoreとStorageで管理する

現在はstaticフォルダに直に置いているのでStorageから配信できるようにする。季節にあったテンプレートなどの追加・削除が便利に。

### Google Cloud Scheduler

データ量を節約して無料のまま運用したいので、Google cloud Schedulerを使ってFunctionsを叩き、一定期間過ぎたデータは自動削除できるようにしたい

### OGP

カードURLを共有したときに、固有のdescriptionとog:imageを設定する。