Vue-Router でのページ遷移(実際はコンポーネント切り替え)の時に Transition を利用したアニメーションを行いたい時があります。
一般的な Transition とは動作の差異があったので、まとめておき対応方法を記録しておきます。
最終的には、以下のようになります
目次
参考
Transition 動作の確認
おそらく一番シンプルな使いかた
Item.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div> <button @click="show = !show">ボタン</button> <transition> <h1 v-if="show">Item</h1> </transition> </div> </template>
<script> export default { data: function () { return { show: false, }; }, }; </script>
<style lang="scss" scoped> .v-enter-active { transition: opacity 3s; opacity: 0; } .v-enter-to { opacity: 1; }
.v-leave-active { transition: opacity 3s; opacity: 1; } .v-leave-to { opacity: 0; } </style>
|
こちらを動作させると、以下のように動作します。
transition にname
を付与すると、transition の class の名称がv
の部分を変更できる。
変更したものが以下の通りです。
Item.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <template> <div> <button @click="show = !show">ボタン</button> <transition name="fade"> <h1 v-if="show">Item</h1> </transition> </div> </template>
<script> export default { data: function () { return { show: false, }; }, }; </script>
<style lang="scss" scoped> .fade-enter-active { transition: opacity 3s; opacity: 0; } .fade-enter-to { opacity: 1; }
.fade-leave-active { transition: opacity 3s; opacity: 1; } .fade-leave-to { opacity: 0; } </style>
|
複数要素の出し入れをアニメーションさせる
ここでは単独の要素の出し入れではなく、複数の要素の出し入れを試してみます。
Item.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div> <button @click="show = !show">ボタン</button> <transition name="fade"> <h1 v-if="show">Item</h1> <h1 v-else>NoItem</h1> </transition> </div> </template>
<script> export default { data: function () { return { show: false, }; }, }; </script>
<style lang="scss" scoped> .fade-enter-active { transition: opacity 3s; opacity: 0; } .fade-enter-to { opacity: 1; }
.fade-leave-active { transition: opacity 3s; opacity: 1; } .fade-leave-to { opacity: 0; } </style>
|
こちらを動作させると次のようになります。
ちがう ちがう、そうじゃない。
新しいアイテムの描画がされてから削除されてしまいます。
こういう時には、transition に mode="out-in"
を付与します。
この mode のデフォルト値は、in-out
です。
修正したのが以下の通りです。
Item.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <template> <div> <button @click="show = !show">ボタン</button> <transition name="fade" mode="out-in"> <h1 v-if="show">Item</h1> <h1 v-else>NoItem</h1> </transition> </div> </template>
<script> export default { data: function () { return { show: false, }; }, }; </script>
<style lang="scss" scoped> .fade-enter-active { transition: opacity 3s; opacity: 0; } .fade-enter-to { opacity: 1; }
.fade-leave-active { transition: opacity 3s; opacity: 1; } .fade-leave-to { opacity: 0; } </style>
|
修正できたので、以下のように削除が先に行われ、追加が後から行われるようになりました。
Vue-Router + Transition の動作確認
続いて Vue-Router と Transition を組み合わせてみます。
Vue-Router と Transition の組み合わせについては、Vue Router - トランジションに解説があります。
シンプルには、次のようにすればいいです。
1 2 3
| <transition> <router-view></router-view> </transition>
|
具体定期に、2 つのItem1.vue
とItem2.vue
を router で切り替えるものを実装してみます。
App.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| <template> <div> <div id="nav" class="has-text-info"> <router-link to="/item1">Item1</router-link>| <router-link to="/item2">Item2</router-link> </div> <div> <transition name="fade"> <router-view /> </transition> </div> </div> </template> <script> import { Transition } from "vue";
export default { name: "App", data: function () { return { show: true }; }, }; </script> <style lang="scss"> .fade-enter-active { transition: opacity 3s; opacity: 0; } .fade-enter-to { opacity: 1; }
.fade-leave-active { transition: opacity 3s; opacity: 1; } .fade-leave-to { opacity: 0; }
#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; }
#nav { padding: 30px; }
#nav a { color: #2c3e50; }
#nav a.router-link-exact-active { color: #42b983; } </style>
|
views/Item1.vue1 2 3 4 5
| <template> <div> <h1>Item1</h1> </div> </template>
|
views/Item2.vue1 2 3 4 5
| <template> <div> <h1>Item2</h1> </div> </template>
|
こちらを動作させると、次のようになります。
んっ?、leave-active と leave-to 効いてなくね?
開発ツールでも確認してみました。
やはりリンクを押してすぐ router-view が書き換えされ、leave-active と leave-to は付与されていませんでした。
SPA ではあるもののページ遷移ではあるので、そういう動作も正しいのだと感じます。
調べてもこちらについての対応方法は見つかりませんでした。
Vue-Router で、Leave 系(leave-active, leave-to)を使うためのアイデア
先に確認した通り、Transition
要素で囲まれたrouter-view
は、leave-active, leave-to が付与されません。
調べても出てこないので、自分なりに対応してみました。以下の実装を行います。
App.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div> <div id="nav" class="has-text-info"> <router-link to="/item1">Item1</router-link>| <router-link to="/item2">Item2</router-link> </div> <div> <router-view /> </div> </div> </template> <script> import { Transition } from "vue";
export default { name: "App", data: function () { return { show: true }; }, }; </script> <style lang="scss"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; }
#nav { padding: 30px; }
#nav a { color: #2c3e50; }
#nav a.router-link-exact-active { color: #42b983; } </style>
|
views/Item1.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <div> <transition name="fade"> <h1 v-if="show">Item1</h1> </transition> </div> </template>
<script> export default { data: function () { return { show: false, }; }, mounted() { this.show = true; }, beforeRouteLeave(to, from, next) { this.show = false; setTimeout(() => { next(); }, 3000); }, }; </script> <style lang="scss" scoped> .fade-enter-active { transition: opacity 3s; opacity: 0; } .fade-enter-to { opacity: 1; }
.fade-leave-active { transition: opacity 3s; opacity: 1; } .fade-leave-to { opacity: 0; } </style>
|
views/Item2.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <div> <transition name="fade"> <h1 v-if="show">Item2</h1> </transition> </div> </template>
<script> export default { data: function () { return { show: false, }; }, mounted() { this.show = true; }, beforeRouteLeave(to, from, next) { this.show = false; setTimeout(() => { next(); }, 3000); }, }; </script> <style lang="scss" scoped> .fade-enter-active { transition: opacity 3s; opacity: 0; } .fade-enter-to { opacity: 1; }
.fade-leave-active { transition: opacity 3s; opacity: 1; } .fade-leave-to { opacity: 0; } </style>
|
mounted でコンポーネント全体を transaction をさせるフラグの書き換え、beforeRouteLeave でもフラグを書き換えてアニメーションさせる時間だけ遅延させます。
こちらを動作させると、以下のように動作します。
表示・削除ともに確認のため 3 秒に設定しているので操作性は落ちますが、例えば 1 秒未満の設定であれば操作性と両立できるでしょう。
beforeRouteLeave の話
今回 beforeRouteLeave を使用してフラグの書き換えと、アニメーションする時間の確保をしました。
beforeRouteLeave は、ナビゲーションガードの機能を目的外の利用をしていると解釈しています。
Vue Router - ナビゲーションガード
本来であれば、以下のことなどに使用するようです。
- ページ遷移の際にログイン済みかチェックして遷移のブロック・遷移先の変更
- ページから離れるときに「本当に離れますか?」などの確認
router-view
以下では、Vue ライフサイクルイベントに加えてナビゲーションガードを使用できます。
確認のために、以下のコンポーネントを実装してみました。
Test.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <template> <div> <button @click="show = !show">ボタン</button> <h1 v-if="show">Test</h1> </div> </template>
<script> export default { data: function () { return { show: false, }; }, beforeCreate() { console.log("beforeCreate"); }, Created() { console.log("Created"); }, beforeMount() { console.log("beforeMount"); }, mounted() { console.log("mounted"); }, beforeUpdate() { console.log("beforeUpdate"); }, updated() { console.log("updated"); }, beforeUnmount() { console.log("beforeUnmount"); }, unmounted() { console.log("unmounted"); }, beforeRouteEnter(to, from, next) { console.log("beforeRouteEnter"); next(); }, beforeRouteUpdate(to, from, next) { console.log("beforeRouteUpdate"); next(); }, beforeRouteLeave(to, from, next) { console.log("beforeRouteLeave"); next(); }, }; </script>
|
こちらを以下のように動作させました。
- Test コンポーネントへの遷移
- Test コンポーネント内での更新
- Test コンポーネントからの遷移
この中で、beforeRouteEnter と beforeRouteLeave が呼び出されることを確認できました。
beforeRouteUpdate
は、同じコンポーネントを使用し/1
から/2
に変わった時などに呼び出しの対象になるそう。
なので今回の確認では呼び出されませんでした。
タイミングとしては、beforeRouteLeave
とbeforeRouteUpdate
は同じになるそう。
今回は、Vue-Router と Transition の動作確認。ページ遷移時のアニメーション発生の対策を行ってみました。
スタイルの記述の順番が大事になったりということがあるので、気をつけて使っていきたいと感じます。
最後にアニメーションをもっとリッチにしてみる実験をしてみました。
ではでは。