20190203のvue.jsに関する記事は10件です。

VueとFirebaseの基本機能全部使ってぬるぬる動くポートフォリオサイトを作ったのでソースと解説

絵描きとかUXとかやりつつフロントもやってる「ゆき」です。ポートフォリオサイトは10年くらい前にMoveableTypeで作ったきり。最近流石に「これでフロントやってますとか言ったら絶対次転職できなくね?」と危機を感じたので0から作り直しました。

サイト: https://pf.nekobooks.com/
ソース: https://github.com/yuneco/portfolio
pf_overview.gif

機能・性能・運用を考えて作った結果、VueとFirebase(Web)の機能を一通り使ったサイトが出来上がりました。これからちょっと凝ったポートフォリオサイトを作りたい方向けに、どういう目的でどの機能を使ったのか、その時のポイントはなんだったのかを共有します。

今回の要件(=ぼくのかんがえたさいきょうのポートフォリオ)

デザイン

  • レスポンシブ!スマホ・PC両対応
  • 全画面でぬるぬる動くアニメーション

Webデザイン自体は専門ではないのですが、絵やUX系もやっているので見た目はがんばりたい。。

機能

  • かわいい・動くトップページ
  • 絵を載せるギャラリーページ
  • アプリ紹介とスキルセット書くページ
  • コンタクトフォーム(メールで飛ばす)
  • Webから新しい絵を追加できる管理ページ
  • ギャラリーページは作品ごとにOGP対応すること

基本的には絵とアプリの紹介を載せるサイトです。
以前のポートフォリオサイトはFlickrに載せた絵をAPIで引っ張って来ていたのですが、そもそもFlickrに絵をアップするのが面倒になってしまった(というかパスワードを毎回忘れる)ので今回はサイト自体に管理者ページを作ります。

性能

  • iPhone6でもぬるぬる動く。目標60fps
  • とにかく軽く。目標ロード時間 < 1s

私見ですが、エンジニアのポートフォリオサイトに軽さは超重要だと思ってます。
プラグイン盛り盛りとか背景に動画使ったりとかは作る身としても避けたい。

やらないこと

  • IE対応

:innocent:だってそういう仕事したくないじゃないですか:innocent:

Firebaseの機能と作ったものの対応

使ったFirebaseの機能 作ったポートフォリオサイトの機能
Authentication サイト管理者機能のログイン
Database ギャラリー画像の一覧・メタデータ管理
Storage ギャラリー画像とサムネイルのアップロード先
Hosting デプロイ先・独自ドメインの接続
Functions サムネイルの自動生成・OGP生成・メール送信

作ったものと解説

ここから、作ったものの機能ベースでVueやFirebaseのどんな機能を使ったのか紹介します。GitHubで公開したソースのリンクもちょこちょこ貼るので、詳細はソースをご参照くださいませ :bow:

Vue.jsのみでトップページ・アニメーション

トップページはSVGを使った全画面のアニメーションになっています(というか元々はこれが一番やりたかった)。固定のアニメーションではなく、女の子のキャラクターが時々アクロバティックに飛んだり跳ねたり、あとギャラリーページで絵を選ぶと、絵の配色に合わせてアニメーションの配色も変わるようになってます。
自己満って言ってしまえばそれまでなのですが、ポートフォリオサイトは仕事ではなかなかできない技術や表現をこだわれる場所なので、こういうのも大切だと思うのです。

アニメーションの種類とライブラリは何を使うか?

この手のアニメーションを作りたいと思った時の選択肢は色々あるので私見でまとめました。

アニメーションの種別 ライブラリ例 Pros(つよみ) Cons(つらみ)
Canvasアニメーション Pixi.jsとかCreate.jsとか WebGLを使えばぬるぬる動かせる。要素が増えても強い。エフェクトやフィルタが豊富 描画面積が負荷に直結するので全画面になると辛い。レスポンシブとRetinaの対応もちょっと辛い。
SVGアニメーション Snap.jsとかSVG.jsとか リソースが軽い(ただしこれはCanvasでもできる)。流行りの流体シェイプみたいにぬるぬる動くデザインができる 流体シェイプ含めSVG固有のアニメーションは重くなりがち(多分GPUレンダリングが効かない)
DOM/CSSアニメーション jQueryとかAnime.js1とか 普通のWebのテクニックが使える。アニメーション以外の機能・要素と相性がいい。GPUが効けば速い 要素が増えると重くなりやすい。GPUレンダリングの効くアニメーションは限定的

今回は熟慮の結果

「SVG・CSSアニメーションを?Vue.jsだけで?作る」

にしました。つまり、Vue.js以外のアニメーションライブラリは使っていません。理由は「圧倒的な軽さ」。今回のアニメーションはサイトの中ではあくまで背景なので、このアニメーションのためにロード待ちになったりスマホがカイロになる事態は避けないといけません。

どうやったか

実装の解説はここではしませんが、面倒なCSSトランジションをプロパティとして扱えるコンテナコンポーネントを作ってアニメーションを構成していきます。

コンテナコンポーネント:
/src/components/anime/core/ECont.vue
要約すると↓こういうコンポーネントです。シンプル :cat: :star:

<template>
  <div @click="clicked" :style="{
      transformOrigin: `${ox}px ${oy}px`,
      transform: `translate3d(${x}px, ${y}px, ${z}px) scale(${s}) rotate(${r}deg)`,
      transition: `
        transform ${dur}ms 0s ${easing},
        transform-origin ${dur}ms 0s ${easing}`
    }"> 
    <slot></slot>
  </div>
</template>

これを組み合わせてキャラクターのコンポーネントを作ります。
https://pf.nekobooks.com/cnfy でデバッグ画面を実際にさわれます)
20180103.gif

もうAnimateCCつかえよ...って話もあるのですが。それでも今回はVueの力試しというのと、やっぱりコンポーネントになって

<!-- 首の角度15度・お辞儀の角度30度(※cnfyはこの子の名前です) -->
<cnfy :headAngle="15" :bowAngle="30" />

みたいに宣言的にかけるのは楽しい。しかもリアクティブに動かせる!Vueたのしい!?

....と、この辺りは書き始めると長くなるので、ニーズがあれば別な記事にまとめようと思います。最終的に、全てのアニメーションがCSSのtransform: translate3d(x, y, z) scale(s) rotate(r)opacityプロパティのトランジションとしてレンダリングされるようにすることで、そこそこややこしいアニメーションでも60fpsが実現できました。

管理(絵のアップロード)ページとギャラリーページ

image.png

楽しいアニメーションができたのでここからは真面目に管理画面を作ります。

Firebase Authでログイン

最初にFirebaseコンソールのAuth機能でgoogleログインを有効化します。
TwitterやFacebookと違って、googleログインであれば「有効にする」をポチッとするだけでおしまい。超楽。

image.png

管理者用のページはfirebase Authを使ってgoogle認証します。
/src/admin/pages/ImgUploader.vue

async mounted () {
  this.user = await AdminApis.Auth.loginWithGoogle()
}

mountedで問答無用でログインに飛ばします。
ログインの実装はこのあたり参照してください。
/src/admin/api/AdminAuth.js

ちなみに、FirebaseAuthを使ったログインの詳しい解説は↓こちらのページあたりが素敵です
Vue.js + Firebase を使って爆速でユーザ認証を実装する

ここでは単にgoogleで認証してもらうことだけが目的で「ログインしたユーザーが管理者かどうか」は判定しません。(クライアントサイドで動いている以上、画面側で判定をしたところでセキュリティ的には大した意味はないので)

正しい管理者かどうかは、DB(firestore)/ストレージ(Firebase storage)のセキュリティルールでチェックして弾きます。(もちろん、通常の利用者が使う画面であれば画面側でもちゃんと判定してあげてください)

allow read;
allow write: if request.auth.token.email == '管理者のgmail';

Firebase Storageにファイルをアップロード

画像のアップロードにはFirebase Storageを使います。
ソースはこのあたり↓

/src/admin/api/ImageUploaderApi.js
/src/admin/pages/ImgUploader.vue#L111

だいたいチュートリアル通りな感じなので問題ないかと。

Firebase Functionsでサムネイルとメタ情報を作成

Storageへのアップロードをトリガーに、サムネイルとメタデータ(タイトル・縦横サイズ・配色情報...etc)を生成する処理をサーバ側で走らせます。このあたりもクライアントで生成してアップロードする方法もあるのですが、将来的にTwitterの自分の投稿から絵を拾ってギャラリーに追加したい、という野望があったので、今回はFunctionに登場してもらいました。
(そうでなくても、不整合を避けたいトランザクション的な処理はサーバ側にまとめておいた方が安全ではあります)

ソースはこの辺り↓
/functions/index.js#L13

functions.storage.object().onFinalize(callback)の形でコールバックを登録します。

サムネイル作成処理の本体はここ↓
/functions/src/generateThumbnail.js#L98
元画像・サムネ共にこのタイミングでキャッシュを有効にしておきます。忘れるとバズった時にあっさり無料枠を使い果たすはずなので注意。

公式のサンプル(Automatically Generate Thumbnails)とほぼ同じですが、ImageMagicを起動する代わりにCanvasに画像を読み込んでJavaScriptで縮小・jpeg生成まで全部やっています。このあたりはお好みで(こっちの方が軽いかなぁ...って思ったけどそんなに変わらない?)。

サムネ生成に続けて、DBにメタ情報を書き込みます。
Cloud Firestoreにサムネと元画像両方のパス・サイズ・画像のメインカラーを保存します。画像を扱うアプリであれば、画像ロード前にレイアウトを確定させるために画像のサイズは是非とも保存しておくべきです。今回は色情報も保存しておいて色付きのプレースホルダーを表示できるようにしています。一枚高々数十KBのサムネですが、モバイルでのUX改善のためには有効な方法です。

image.png

Cloud Firestoreでメタ情報の更新と画像URLの書き込み

DB書き込み後、管理画面側でDB更新をトリガにメタ情報の更新画面を表示します。タイトルや画像の説明(今の所画面には表示していませんが)を編集するのと、画像のダウンロードURLを生成するのが目的です。

本来であればStorageにアップした画像のダウンロードURLは、一つ前のFunctionsの中で生成してDBに保存すべきなのですが、これが今の所これが簡単にはできません。クライアント用のFirebase SDKでは一行ですむURL取得が、Functionsで使えるAdmin SDKだとサービスアカウントを作って面倒なプロセスを踏まないといけないようです。。

詳細は下のStackOverFlowの回答が詳しいです。一応簡単に生成するための逃げ道はあるようですが、正攻法ではないので今回はパスしました。

Get Download URL from file uploaded with Cloud Functions for Firebase

Cloud Firestoreからギャラリー画像のメタデータを取得

公開(非管理者)ページに戻ってギャラリーページを作ります。
ギャラリーはこのサイトのメイン機能の一つなので、トップページを表示した時点でCloud Firestoreからデータを取得します。
/src/api/ImgListApi.js#L14

今の所数十件なので全件まとめて取ってきていますが、もし数百になるならページングを考えるべきかもしれません。
また、DBのロードが終わったら順次サムネイルのプリロードも走らせています。

VueRouterによる個別のギャラリー画像へのリンク対応

やっぱり新作をUPしたらその絵に直リンクしたいですよね。GAでログを取るためにも、絵一つ一つにURLでアクセスできる必要があります。
こんな感じ↓のURLで個別の絵にURLでアクセスする仕組みを作ります。
https://pf.nekobooks.com/gallery/1547483353992_933

アクセスの制御はVueRouterで画像のIDをパラメータとして受け取って、そのIDと画像リストの選択項目を連動させます。ただ、それだけではアプリ内のリンクは動作しますが、URL直リンクで飛んできた時にはうまく動きません。VueRouterから画像のIDを受け取った時点ではまだDBのロードが終わっていないからです。

今回はDBロードをwatchsで監視して、ロードされたデータがセットされたタイミングで連動を走らせることで対処します。
/src/pages/GalleryView.vue

画像のグリッドレイアウトをVueでがんばる

色々プラグインはあるようですが、今回は普通にVueコンポーネントで手書きします。普通に座標を計算してposition: absoluteでdivを並べているだけです。(CSS的にはグリッドレイアウトを使うべきなのかもですが、今回は選択やリサイズ時にぬるぬる動かしたかったのでパス)
/src/components/PhotoList.vue

仕事だとなかなか難しい場面も多いですが、この程度のものであればプラグインやたらと組み込むよりも手書きした方が細かい調整利くし勉強にもなりますね。

Firebase Functionsでメールフォームを作る

今日日メールとかいらなくない?という話もあるのですが、まあお約束ということで。
画面は手抜き感満載ですが許してください。。

image.png

FirebaseFunctionsでメールを送信

メール送信にはFunctionsを使ってWebAPIを作ります。

exports.contactmail = functions.https.onRequest(callback)

のような形でFunctionを作るとWebからアクセスできるcontactmailという名前のFunctionができます。実際にアクセスするURLはFirebaseのコントールから確認できます。

image.png

サーバ側のメール発信処理にはnodemailerを使います。こちらの記事が超絶丁寧です↓
VuejsとFirebaseでメール送信機能を実装する

この記事にだいたい書かれているので注意点だけ箇条書きすると、

  • Firebaseの無料プランではgoogleの外へ通信ができないため、無料プランで頑張る場合はgmailを使う
  • 発信に利用するgmailのアカウントはセキュリティレベルを落とす必要あり。Firebaseのログインやサイトの管理者アカウントとしてセキュリティルールに設定したものとは別のアカウントを利用すべきです(gmailで転送設定をすれば好きなアカウントで受信できます)
  • ID/パスワードはソースに直書きせずに環境変数を利用すること

環境変数は

firebase functions:config:set admin.contact.mail="メアド" admin.contact.pass="パス"

を(ローカルの)コマンドラインで叩けば、Functions側から

const config = functions.config()
const mail = config.admin.contact.mail
const pass = config.admin.contact.pass

のように簡単に取得できます。

あと今回はメールの送信先が完全固定なので大丈夫ですが、送信先もなんらかの条件で動的に変更する合、Functionsの呼び出しパラメータをいじって任意のアドレスにメールを飛ばせてしまうことのないよう、厳重にチェックが必要です。スパムメールの踏み台やなりすまし詐欺に悪用されてしまったら目も当てられないので気をつけましょう(こわい)。

Firebase Hosting で独自ドメイン公開

Firebase Hostingで公開するサイトはデフォルトではhttps://プロジェクトID.firebaseapp.comのドメインで公開されます。これだけでも十分ありがたいのですが、やはりポートフォリオサイトなので独自ドメインを使いたいですよね。

FirebaseコンソールのHosting画面から「ドメインを接続」を押すと、自分の所有するドメイン・サブドメインをFirebaseのサイトにつなぐことができます。ドメインをどこでとったかにもよるのですが、大体同じような流れです。↓こちらの記事はお名前.comの場合のやり方を解説してくれています。

Gatsby+firebaseで独自ドメインのHTTPSサイトを作る(その2 Firebaseの設定)

ギャラリーページのOGP対応

ギャラリーの絵ひとつひとつにURLでアクセスできるようにしたのでTwitterで新作を宣伝できるようにはなったのですが、やっぱりそこまでやったらOGPしたいですよね。

今回はギャラリーページは作品の画像とタイトルを使い、その他のページは一律のOGPとしました。

トップページ
image.png
ギャラリーページ(の個々の作品)
image.png
この部分のやり方は年末にアドベントカレンダーでかなり詳しく書いたので、手前味噌ですが↓こちらをご参照くださいませ。
SNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だった
今回は手抜きのリダイレクト方式で、かつ画像も動的生成ではなくStorageにアップロード済みの画像のURLをそのまま返しています。

性能評価と最適化

キャッシュなしでLoadまで1秒(1.6MB|1.1minってあるのはLoad後にギャラリーのサムネを裏でゆっくり読んでるから)。アニメーション用のSVGは全てapp.jsの中に含まれています。
image.png
管理者機能部分をチャンク分割していますが、他の最適化はそれほどやっていません(この辺りは勉強不足なのであまり語れない...どなたかこれでいいのか教えてください)。
VueRouter-遅延ローディングルート

image.png
Auditsでもかなりいいスコアが出てます。ただ、正直な実感としてはアクセシビリティやSEOにはあまり配慮していないのでこの点数は高過ぎ気がします。この辺りはVueのテンプレートが形だけよしなにやってくれてしまっているせいなのかもしれません。

今後のタスク(備忘+自戒)

  • AtomicDesign?なにそれ?みたいなコンポーネント分割をなんとかしたい
  • ファビコンつくる
  • ギャラリー画像のタイトル再編集・並べ順変更
  • キャラの表情・衣装のバリエーションを増やす(趣味全開)

まとめ

最近はWebページを作るツールやサービスもどんどん進化していて、ちょっとしたサイトならノンコーディングでもそれなりにできる時代になってしまいました。そんな中でも最近の技術をキャッチアップしつつ自分で組んでみると中々勉強になるものです。特にデザイナさんやエンジニアさんであれば、トレンドを取り入れつつ自力でしっかり実装しているサイトは良いPRになるはず。この記事を見てポートフォリオサイト作ってみようかなー、って思ってくれる人がいたらとても嬉しいです。

  • :fire: ポートフォリオサイトをがっつり作るとVueとFirebaseの基本機能を一通り習得できるよ
  • :relaxed: 全画面アニメーションするならSVGが軽くて最強!専用のライブラリを使わなくても、Vueだけで結構作れちゃう
  • :cat: エンジニアやデザイナーはみんなもっとオリジナルのポートフォリオサイト作ろう
  • :innocent: ポートフォリオ作ったらブログとかQiitaとかに記事も書こう

  1. SVGも使えるらしい 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでForm部品をcomponent化してStoreで管理

多様なFormモジュールが増えても対応できるように各部品をComponent化して、且つvalueの値をStoreのstateで管理したいと思い、実装しました

component化したのは下記部品です

  • input type="text"
  • textarea
  • button type="submit"

開発環境

package.json
"vue": "^2.5.22"
"vuex": "^3.1.0"

input type="text"

  • componentのInputText.vueは汎用性を持たすためpropsを元に展開
  • Add.vueに$emitのイベント購読名(eventName="hogeTitle")を定義してInputText.vueにpropsで渡す
  • イベントハンドラtitleの値をcommit()でStoreのstateにアクセスして渡す
  • inputのvalueが更新されるとStoreのmutationsでstateを更新
Add.vue
<template>
  <div>
    <input-text
      name="hogeTitle"
      :model="hoge.title"
      placeholder=""
      eventName="hogeTitle"
      @hogeTitle="title"
    >
    </input-text>
  </div>
</template>

<script>
import InputText from './form/InputText';

export default {
  data() {
    return {
      hoge: {
        title: '',
      },
    };
  },
  methods: {
    title(value) {
      const data = { title: value };
      this.$store.commit('addData', data);
    },
  },
  components: {
    InputText,
  },
};
</script>
./form/InputText.vue
<template>
  <div>
    <input
      type="text"
      :name="name"
      :value="model"
      :placeholder="placeholder"
      @input="updateValue"
    >
  </div>
</template>

<script>
export default {
  props: {
    name: String,
    model: String,
    placeholder: String,
    eventName: String,
  },
  methods: {
    updateValue(e) {
      this.$emit(this.eventName, e.target.value);
    },
  },
};
</script>
store.js
const state = {
  add: {
    title: '',
    text: '',
  }
};

const mutations = {
  addData(data, value) {
    state.add = Object.assign({}, { ...data.add }, { ...value });
  },
};

export default {
  state,
  mutations,
};

textarea

  • input type="text"とほぼ同じ
  • 違うのはpropsにrowsの値を入れてるだけ
  • store.jsは割愛
Add.vue
<template>
  <div>
    <input-textarea
      name="hogeText"
      :model="hoge.text"
      placeholder=""
      eventName="hogeText"
      rows="10"
      @hogeText="text"
    >
    </input-textarea>
  </div>
</template>

<script>
import InputText from './form/InputTextarea';

export default {
  data() {
    return {
      hoge: {
        text: '',
      },
    };
  },
  methods: {
    text(value) {
      const data = { text: value };
      this.$store.commit('addData', data);
    },
  },
  components: {
    InputTextarea,
  },
};
</script>
./form/InputTextarea
<template>
  <div>
    <textarea
      :name="name"
      :value="model"
      :rows="rows"
      :placeholder="placeholder"
      @input="updateValue"
    >
    </textarea>
  </div>
</template>

<script>
export default {
  props: {
    name: String,
    model: String,
    placeholder: String,
    eventName: String,
    rows: String,
  },
  methods: {
    updateValue(e) {
      this.$emit(this.eventName, e.target.value);
    },
  },
};
</script>

button type="submit"

  • componentのSubmitButton.vueは汎用性を持たすためpropsを元に展開
  • Add.vueに$emitのイベント購読名(eventName="saveData")を定義してSubmitButton.vueにpropsで渡す
  • イベントハンドラsaveでStoreで管理してたinputの値(state.add)をcommit()でStoreのstateにアクセスしてデータをpush
Add.vue
<template>
  <div>
    <submit-button
      eventName="saveData"
      text="保存"
      @saveData="save"
    >
  </div>
</template>

<script>
import SubmitButton from './button/SubmitButton';

export default {
  methods: {
    save() {
      this.$store.commit('saveData', this.$store.state.add);
    },
  },
  components: {
    SubmitButton,
  },
};
</script>
./button/SubmitButton
<template>
  <div>
    <button @click="save" type="submit">{{text}}</button>
  </div>
</template>

<script>
export default {
  props: {
    eventName: String,
    text: String,
  },
  methods: {
    save() {
      this.$emit(this.eventName);
    },
  },
};
</script>
store.js
const state = {
  hoge: {}
  add: {
    title: '',
    text: '',
  }
};

const mutations = {
  saveData(data, addData) {
    state.hoge.push(addData);
  },
  addData(data, value) {
    state.add = Object.assign({}, { ...data.add }, { ...value });
  },
};

export default {
  state,
  mutations,
};

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue.jsとfirebaseでサクッと認証周り実装

めんどくさい

認証周りの実装は楽しくないし、パスワードの再設定やらメールの設定やら考えることが結構多いです。
そこで、エンジニアらしく怠惰になるべく、firebaseさんに力をお借りすることにしました。

お借りした機能

  • ユーザーの作成
  • ユーザーログイン
  • パスワード再設定
  • メール認証

実装

firebaseのプラグイン追加 参考

import firebase from 'firebase'

const config = {
  apiKey: '',
  authDomain: '',
  databaseURL: '',
  projectId: '',
  storageBucket: '',
  messagingSenderId: ''
}

firebase.initializeApp(config)

export default firebase

↑のfirebaseを呼び出して使っていきます。

store/user.js

import firebase from '../plugins/firebase'

export const user = {
  namespaced: true,

  state: () => ({
  }),

  mutations: {
  },

  getters: {
    isAuth (getters) {
      // メール認証するまで
      const user = firebase.auth().currentUser
      return user ? user.emailVerified : false
    }
  },

  actions: {
    // ユーザー作成
    async createUser ({commit, dispatch}, {email, password}) {
      const auth = firebase.auth()

      await auth.createUserWithEmailAndPassword(email, password)
        .then(async () => {
          await dispatch('confirmEmail')
        })
        .catch(error => {
          dispatch('alertMessage', {errorCode: error.code, errorMessage: error.message})
        })
    },
    async confirmEmail () {
      const user = firebase.auth().currentUser

      user.sendEmailVerification().then(() => {
        alert('確認メールを送信しました。メールアドレスをご確認ください')
      }).catch(error => {
        console.log(error)
      })
    },
    // パスワードの再設定
    async sendPasswordResetEmail ({dispatch}, email) {
      const auth = firebase.auth()

      auth.sendPasswordResetEmail(email)
      .then(() => {
        alert('メールを送信しました')
      }).catch(error => {
        dispatch('alertMessage', {errorCode: error.code, errorMessage: error.message})
      })
    },
    async login ({dispatch}, {email, password}) {
      firebase.auth().signInWithEmailAndPassword(email, password)
        .catch(function (error) {
          dispatch('alertMessage', {errorCode: error.code, errorMessage: error.message})
        })
    },
    // アラートメッセージ
    alertMessage ({commit}, {errorCode, errorMessage}) {
      switch (errorCode) {
        case 'auth/wrong-password':
          alert('パスワードが違います')
          break
        case 'auth/invalid-email':
          alert('無効のメールアドレスです')
          break
        case 'auth/user-not-found':
          alert('ユーザーが存在しません')
          break
        case 'auth/weak-password':
          alert('6文字以上でパスワードを設定してください')
          break
        case 'auth/email-already-in-use':
          alert('すでに存在しているメールアドレスです')
          break
        default:
          alert(errorMessage)
          break
      }
    }
  }
}

storeにユーザーモジュールを追加し、作成しました。
storeを使い実装することで、vueファイルにfirebaseのコードを書かないよう努めました。
このuser.jsのコードをvueファイルから呼び出すだけで使用することができます。

login.vue
Vuetifyを使用しております。

<template>
  <div>
    <v-layout align-center justify-center>
      <v-flex xs12 sm8 md4>
        <v-dialog v-model="dialog">
          <v-card>
            <v-card-title class="title elevation-1">ログイン</v-card-title>
            <v-card-text>
              <v-form>
                <v-text-field
                  v-model="email"
                  label="学校メールアドレス"
                />
                <v-text-field
                  v-model="password"
                  label="パスワード"
                />
              </v-form>
            </v-card-text>
            <v-card-actions>
              <v-spacer></v-spacer>
              <v-btn @click="submit" class="white--text">ログイン</v-btn>
            </v-card-actions>
          </v-card>
        </v-dialog>
      </v-flex>
    </v-layout>
  </div>
</template>
<script>
import { mapActions } from 'vuex'

export default {
  data () {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    ...mapActions({
      login: 'user/login'
    }),
    async submit () {
      await this.login({email: this.email, password: this.password})
    }
  }
}
</script>

今回はログインの機能のみのvueファイルを書いています。
反響があれば、追記していきます。
ざっくり書いている部分が多いので、細かい解説や他のvueファイルなどはちょっとずつ追記していきます。

終わり

firebase全く無知の状態からほんの数時間(他のユーザー作成などの部分含め)で、実装することができたので
実装コストがかからず本当に良いと思います。

個人的によかったのは、ログインなどでエラーになった際に適切なエラーコードを返してくれるところです。
これのおかげでユーザーに適切なエラーメッセージを伝えることができます。

スクリーンショット 2019-02-03 18.01.47.jpg
スクリーンショット 2019-02-03 18.02.03.jpg

エラーコードに関する詳細firebaseドキュメント

最後に

firebase最高。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゼロからVue.jsを触ってみる 第5回

前回の続きでdirectives紹介をしていきます。

Directivesの続き

v-on

DOMのイベントをlistenして、イベント発火時にメソッドをトリガーできます。

<template>
  <p v-on:click="handleClick">Click me</p>
</template>

<script>
export default {
  methods: {
    handleClick: function() {
      alert('test')
    }
  }
}
</script>

この例の場合、Click meというところをクリックするとhandleClickというメソッドが発火してtestというアラートが表示されます。

このv-onにはショートハンドが用意されており、v-onと書く代わりに@で代用できます。

ショートハンドで書き直した例
<a @click="handleClick">Click me!</a>

(Qiitaのシンタックスハイライトには対応してないっぽいですね。)

click以外にもイベントはあり、submitなどもよく使います。

Event Modifiers

v-onのイベントに対してEvent Modifiersというものが用意されています。

例えば.preventというEvent Modifierを使うと自動的にpreventDefault()を呼んでくれます。

他にも.stop .prevent .capture .self .once .passiveなどがあります。

ここでは解説しません。詳細は公式ドキュメントを参考にしてください。

元のイベントオブジェクトにアクセスする

$eventという変数を使って元のイベントオブジェクトをメソッドに渡すことができます。

<template>
  <p @click="handleClick($event)">Click me</p>
</template>

<script>
export default {
  methods: {
    handleClick: function(event) {
      console.log(event);
    }
  }
}
</script>

上記の例ではClick meをクリックするとコンソールにMouseEventのオブジェクトが表示されます。

このMouseEventオブジェクトに関していえば例えばShiftキーを押しながらのクリックかどうか?みたいなことだったり、ほかにもいろんな値が入ってるのでそういったイベント関連の情報を引っ張ってきて処理をしたいときに便利です。

v-show

表示したり隠したりできます。値がTruthyなら表示、Falsyなら非表示(display: none)になります。

<template>
  <p v-show="isThree(3)">3です</p>
</template>

<script>
export default {
  methods: {
    isThree: function(num) {
      return num === 3;
    }
  }
};
</script>

上記の例ではisThree()の引数に3を与えるとTrueになって「3です」と表示されます。引数を3以外にするとFalseになって何も表示されません。


Directivesに関してはこれで大体一通り見たかな?と思うので今回はここまで。もっと細かいところは公式ドキュメントなどを参考にお願いします。

次回はそろそろDirectives以外を見ていきます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 基礎 [クリックイベント/データバインド]

Vue.jsが気になっていたので、猫本を買ってきました。
しばらく自分のOUTPUTに記事を書いてこうと思います。

クリックイベントとデータバインド

hoge.js
var app = new Vue({
    el: '#app',
    data: {
        show :true,
        message :'HelloWorld!!',
    },
    methods: {
        handleClick: function(event) {
            if( app.show == false)
            {
                app.show = true;
            }
            else
            {
                app.show = false;
            }
        }
    }
})
hoge.html
<div>
    <p v-if="show">{{message}}</p>
    <input v-if="show" v-model="message">
    <button v-on:click="handleClick">button</button>
</div>

理解

  • divタグのid"app"と、jsの"#app"が紐づく
  • dataはモデル、これがバインドされる
  • methodsはイベント、ここにクリックイベントとか書くらしい

ボタンを押すとラベルと入力フォームが消えたり表示されたりします。
入力フォームの値が、ラベルにバインドされているのでリアルタイムで連動します。

バインドがWPFのMVVMよりわかりやすくてとても良い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 基礎 【クリックイベント/データバインド】

Vue.jsが気になっていたので、猫本を買ってきました。
しばらく自分のOUTPUTに記事を書いてこうと思います。

クリックイベントとデータバインド

hoge.js
var app = new Vue({
    el: '#app',
    data: {
        show :true,
        message :'HelloWorld!!',
    },
    methods: {
        handleClick: function(event) {
            if( app.show == false)
            {
                app.show = true;
            }
            else
            {
                app.show = false;
            }
        }
    }
})
hoge.html
<div>
    <p v-if="show">{{message}}</p>
    <input v-if="show" v-model="message">
    <button v-on:click="handleClick">button</button>
</div>

理解

  • divタグのid"app"と、jsの"#app"が紐づく
  • dataはモデル、これがバインドされる
  • methodsはイベント、ここにクリックイベントとか書くらしい
  • {{}}のことをMustacheという

ボタンを押すとラベルと入力フォームが消えたり表示されたりします。
入力フォームの値が、ラベルにバインドされているのでリアルタイムで連動します。

バインドがWPFのMVVMよりわかりやすくてとても良い。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-cli3でbuildした時にTypeError: Cannot read property 'minify' of undefined

概要

vue-cli3で生成したばかりのプロジェクトでbuildが出来なかったので調べた所、直近出現したバグだったので日本語でも残しておこうと思います。

解決策

terser@3.4.0 のバグなので

package.json
"resolutions": {
  "terser": "3.14.1"
}

と記述して

npm install

または

npm i -D terser@3.14.1

とかでバージョンを下げれば大丈夫:thumbsup:

参考

https://github.com/vuejs/vue-cli/issues/3407#issuecomment-459985313

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分用メモ(v-if、v-show)

v-ifとv-showの違いと使用する判断基準

  • 表示/非表示を頻繁に切り替えるコンテンツにはv-show → displayプロパティで非表示にしてる。
  • 最初に表示(非表示)にしたら滅多に変更しないものはv-if → DOMを破棄してる。

他にも判断基準はあるはずなので、追記していく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VueLoaderPluginのせいでエラーが起きていると言われたら確認したいこと

概要

土曜日なので、vueはじめました。
はじめてのwebpackに戸惑いながらもSPAに挑戦しています。

しかし、さっそくのビルドエラーに遭遇してしまいました。
なにやらvue-loaderが悪さをしているようです。

ERROR in ./src/pages/About.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

今回はわたしが解決した方法をご紹介します。

原因

https://vue-loader.vuejs.org/migrating.html
どうやらvue-loaderには最近著しい変更があり、v15からプラグインとして読み込んであげてねってことになったようです。

ちなみに、自分の環境だとバージョンは"vue-loader": "^15.6.2",
まさにこれでした。

解決方法

https://vue-loader.vuejs.org/migrating.html
以下のようにconstで定義して、module.exportsのなかのpluginsにぽこっとnewしてあげればいいようです。

// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  // ...
  plugins: [
    new VueLoaderPlugin()
  ]
}

私はこれで無事に解決できました。
もし、同じエラーが出た場合はお試しくださいね!

おまけ

次はcss-loaderを解決しなくては..汗
今までgulp一本な人生だったので、webpackは不思議な感覚です。(かといって、gulpをそんなに高い頻度で使っているわけでもないw)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Onsen UI、Vue.jsでアプリを作る初めの一歩(開発→本番)

はじめに

初めの一歩ができると、わかる人はどんどん進んでいったりすると思っています。
ちなみにOnsen UIはCSSのライブラリっぽいです。
よろしくお願いします。:unicorn:

Onsen UIのサイトはこちらから
以下記事の進行は
Onsen UI、Vue.js
を元に進めて行きます。
では早速!

開始

ターミナルを開いて、Onsen UI + Vueプロジェクトの設定を簡単にするために、Vue CLIをインストールします。以下コマンド。

$ npm install -g @vue/cli

インストールが終わるとプロジェクトが早速作れるようになっているので作りたい場所(フォルダ)まで移動して以下コマンドを打ちます。打つと質問されるので答えて行きましょう!!
最初の質問は私だけかな?通信のせい?とりあえずnにしました。わら
ここでの[my-onsen-app]は作るプロジェクトのフォルダ名になります。
ちなみに名に大文字入れるとエラーになります:frowning2:

$ vue create my-onsen-app

?  Your connection to the default npm registry seems to be slow.
   Use https://registry.npm.taobao.org for faster installation? (Y/n)n 

上記、デフォルトのnpmレジストリへの接続は遅いようです。より速いインストールのためにhttps://registry.npm.taobao.orgを使用しますか? と聞かれているのでnをうって断りました。

Vue CLI v3.4.0
? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint) 
  Manually select features 

ここでは、デフォルトの設定か自分でカスタマイズされているかなので、最初は上記のdefaultでいいと思います。そのままエンターで。

っとするとプロジェクトの生成が始まります!少し待ちましょう!
そして終了すると以下のようなフォルダ構成になります。treeで全部表示しようと思いましたがあまりにもフォルダがありすぎてちょっとあれだったので割愛します。

スクリーンショット 2019-02-02 23.31.01.png

できたプロジェクトに移動してください。以下コマンド

$ cd my-onsen-app

以下コマンドで今度はonsenuiとvue-onsenuiをプロジェクトにインストールします。

$ npm install onsenui vue-onsenui

次に以下画像にある、main.jsをお好きなエディタで開いてください。
スクリーンショット 2019-02-02 23.35.46.png

開くと以下のようになっているので書き足していきます。

main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')


書き足すと以下のようになると思います。
要は、インストルしたcssを読み込み、VueファイルでOnsen UIを使うようにしたということですね。

main.js
import 'onsenui/css/onsenui.css';
import 'onsenui/css/onsen-css-components.css';

import Vue from 'vue'
import VueOnsen from 'vue-onsenui';
import App from './App.vue'

Vue.config.productionTip = false
Vue.use(VueOnsen); 

new Vue({
  render: h => h(App),
}).$mount('#app')

次に、以下画像のApp.Vueをお好きなエディタで開いてください。そのファイルに以下内容をコピペしてください。
スクリーンショット 2019-02-02 23.41.15.png

App.Vue
<template id="main-page">
  <v-ons-page>
    <v-ons-toolbar>
      <div class="center">Title</div>
    </v-ons-toolbar>

    <p style="text-align: center">
      <v-ons-button @click="$ons.notification.alert('Hello World!')">
        Click me!
      </v-ons-button>
    </p>
  </v-ons-page>
</template>

全て終わったら以下コマンドで開発環境を立ち上げます。

$ npm run serve

終わると、http://localhost:8080/ で開いて見てねと書いてあるので貼り付けて見てみましょう!!以下の画像が出て、[Click me!]タップでアラートが出ます。
スクリーンショット 2019-02-02 23.46.23.png

で、色々開発で試したあとは本番にのせたいですよね!
まず一回ローカル環境を閉じるため、macならControl+Cを押します。そしたら以下コマンドを打ちます。

$ npm run build

と以下のように、distというフォルダができるのでそのフォルダの中身を公開するところに一式おいてあげればいいです。

:bangbang:ちなみに、デフォルトの設定だとエラーが出る可能性があります。エラー解決は以下リンクからお願いします。
TypeError: Cannot read property 'minify' of undefined

参考動画.gif

で、ここでそのあげるのに何使えばいいのかなという方に朗報です!!
簡単にWebアップまでの記事を以下で書いたの是非!!先ではstaticフォルダとindex.htmlだけのところをここのファイル一式に変えればOKです!!

FirebaseにてWeb(https、無料)を公開してみるをやる

参考

ここのサイトはVue.jsについてとてもわかりやすそうなサイトです!!
https://www.monster-dive.com/blog/web_creative/20180608_001789.php

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む