Vue.js と Vue-router でアニメーション付きのページ遷移

更新日
2017.06.23
作成日
2017.05.14

Vue-router を使った SPA サイトのページ設計と遷移アニメーションを作ります。

前回 Vue-cli で作った学習用のプロジェクトを引き続き使って Vue-router をもっとちゃんと使っていきます。全体のコードはここのリポジトリあります。

Vue-router とは

Vue-router は SPA サイトを作るためのルーティング処理をサポートする Vue の拡張ライブラリです。特定の URL とコンポーネントを結びつけます。

出来上がったデモ

公式のカートのサンプルを真似して JavaScript のモックAPIにしたので追加や削除も機能するようになりました。リロードしなければデータを維持します。

ハッシュを付けない設定

ルーターのコンストラクタオプションでヒストリモードにするとハッシュが付かなくなります。リロードや URL に直接アクセスしたときに Not Found になるので .htaccess でリライトモジュールの設定をします。このプロジェクトの場合はサブディレクトリがあるのでこんな感じです。

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /vue-test/
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

ネストされたルート

1層目にホームとメンバーページがあって、メンバーページの中に2層目のメンバーリストとメンバー詳細ページを作ります。

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

export default new Router({
  // ヒストリモードはハッシュがつかなくなる ※mod_rewrite等が必要
  mode: 'history',
  // サブディレクトリがある場合ベースに設定する
  base: '/vue-test/',
  routes: [
    {
      path: '/',
      component: require('@/components/PageHome')
    },
    {
      path: '/member',
      component: require('@/components/PageMember'),
      // ネストされたルート
      children: [
        {
          path: '/',
          component: require('@/components/member/PageList')
        },
        {
          path: ':id',
          name: 'member-detail',
          component: require('@/components/member/PageDetail')
        }
      ]
    }
  ]
})
  • http://localhost/vue-test/member メンバーの一覧を表示
  • http://localhost/vue-test/member/1 メンバー1の詳細を表示

ルートをネストすると <router-view> もネストされるので、別のトランジションエフェクトを付けたりカテゴリのサイドバーを残して一部だけを遷移させるとかが出来るようになります。ツイッターのポップアップモーダル的な遷移も多分これで出来そうです。

ところでこの辺の view 用コンポーネントとその他のコンポーネントのディレクトリ構成ってみんなどんな風にやってるんでしょうか。わたし気になります!

ルータービューの表示

ルートに該当したコンポーネントを表示させたい場所に <router-view> を記述します。ルートがネストされている場合は親になる view 用コンポーネントの中に <router-view> を記述します。

ルーターリンクと現在の位置をハイライト

ルーターリンクを使っていれば自分と親のパスに自動的に .router-link-active というクラス名が付きます。ホームなど「/」のパスがある場合それに続くパスのページを見ていると「/」も常にアクティブになってしまうので exact を付けると良いです。

<router-link to="/" exact>Home</router-link>

IDなどのパラメータを付けたい場合は以下のようにパメータを渡せます。単純ならテンプレートリテラルとか使って書いても良い。

<router-link :to="{ name:'member-detail', params: { id: val.id }}">{{ val.name }}</router-link>

ページ遷移エフェクトを作る

お試しだったので結構長めのエフェクトなんですがルートの遷移中にオーバーレイで SVG を表示させるようにしました。transition で囲むだけだと凝ったものを作るのが難しそうだったので、ローディングオーバーレイ用のコンポーネントを作ってそれをルーターのトランジションのイベントと連動させています。

コンポーネントにしたので複雑なアニメーションも作りやすいです\(*⁰▿⁰*)/

<transition name="fade" mode="out-in"
@before-leave="$refs.overlay.start()" @enter="$refs.overlay.end()">
  <router-view class="view"></router-view>
</transition>
<view-loading ref="overlay"></view-loading>

ちなみにトランジションのキーを $route.path にしてもトリガー出来ましたが、要素を enter / leave で分ける必要なかったので上のようにしました。あとこれするとネストされてても全部のルート遷移で反応しちゃうので色々処理しないと面倒くさそうです…。

<transition>
<div :key="$route.path">...</div>
</transition>

2層目のメンバーページのルータービューにはまた別の比較的シンプルなエフェクトを付けました。

初期データの読み込み

最初から表示させるデータを読み込んでから遷移完了させたい場合はナビゲーションガードの beforeRouteEnter を使用します。

export default {
  beforeRouteEnter(route, redirect, next) {
    store.dispatch('member/get', parseInt(route.params.id)).then(() => {
      next() // 読み込み終わったら next() で遷移させる
    })
  }
}

Vue-router を使うと以下のようなナビゲーションガードが使用できるようになりコンポーネント側から遷移をコントロール出来ます。

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

開発中ホットリロードで beforeRouteEnter が実行されずにデータが読み込まれない事があるのでその場合は以下のように対策してます。

export default {
  created() {
    if (process.env.NODE_ENV === 'development') {
      store.dispatch('member/get', parseInt(this.$route.params.id))
    }
  }
}

※ webpack.DefinePlugin で NODE_ENV を利用できるようにしておく。

<keep-alive>

ルータービューに <keep-alive> を設定しておくと状態をキャッシュするので関連したページを分ける時に凄く便利でした!

mounted などのライフサイクルは更新が必要な場合にだけ呼び出されるようになります。例えば関係ないページに移動したときだけ beforeDestroy が発生してデータを破棄させたり出来ます。

まとめ

SPA はフロント作業を Vue だけに集中出来るのでデザインを作るのに PHP とかと行ったり来たりするよりも凄く楽です!また後でサーバーレンダリングも試してみたいです(ずっと言ってる)。今のままだと誰でも編集できちゃうよみたいな感じなので次回は認証関係をやる予定です多分(•ө•)ノ