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

最終更新日
2017.10.31

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 を表示させるようにしました。

単純に router-view を囲むだけだと遷移先の読み込みが終わるまでトランジションが始まらないので、ViewLoading.vue というコンポーネントを作り、ストアにあるフラグを使って表示・非表示させるようにしました。beforeEachafterEach でトランジションのキーを更新してますが、ネストがあると子ルートを区別するのは少し複雑になりますね。メンバー一覧のところだけわざと遅延をいれてますが実際はロードが長いページだけ表示されます。

overlay は View の為だけのデータなのでコンポーネントで持ちたい所ですが、このコンポーネントの為だけではなく「今サイトはこんな状態だよ」という解釈にすればまあなんとか。

コンポーネントにすると複雑なアニメーションも作りやすい(๑'ᴗ'๑)

router.beforeEach((to, from, next) => {
  store.commit('setOverlay', true)
  next()
})
router.afterEach((to, from) => {
  store.commit('setOverlay', false)
})

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

初期データの読み込み

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

export default {
  beforeRouteEnter(route, redirect, next) {
    store.dispatch('member/get', 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', this.$route.params.id)
    }
  }
}

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

<keep-alive>

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

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

おわりに

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