Vue-Router でのページ遷移(実際はコンポーネント切り替え)の時に Transition を利用したアニメーションを行いたい時があります。
一般的な Transition とは動作の差異があったので、まとめておき対応方法を記録しておきます。
最終的には、以下のようになります

目次
参考
Transition 動作の確認
おそらく一番シンプルな使いかた
Item.vue| 12
 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.vue| 12
 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.vue| 12
 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.vue| 12
 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 - トランジションに解説があります。
シンプルには、次のようにすればいいです。
| 12
 3
 
 | <transition><router-view></router-view>
 </transition>
 
 | 
具体定期に、2 つのItem1.vueとItem2.vueを router で切り替えるものを実装してみます。
App.vue| 12
 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.vue| 12
 3
 4
 5
 
 | <template><div>
 <h1>Item1</h1>
 </div>
 </template>
 
 | 
 
views/Item2.vue| 12
 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.vue| 12
 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.vue| 12
 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.vue| 12
 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.vue| 12
 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 の動作確認。ページ遷移時のアニメーション発生の対策を行ってみました。
スタイルの記述の順番が大事になったりということがあるので、気をつけて使っていきたいと感じます。
最後にアニメーションをもっとリッチにしてみる実験をしてみました。

ではでは。