# Transitions
-Since the `<router-view>` is essentially a dynamic component, we can apply transition effects to it the same way using the `<transition>` component:
+In order to use transitions on your route components and animate navigations, you need to use the [v-slot API](../../api#v-slot):
```html
- <router-view v-slot="{ Component }">
- <transition>
- <component :is="Component" />
- </transition>
- </router-view>
+<router-view v-slot="{ Component }">
+ <transition name="fade">
+ <component :is="Component" />
+ </transition>
+</router-view>
```
[All transition APIs](https://vuejs.org/guide/transitions.html) work the same here.
## Per-Route Transition
-The above usage will apply the same transition for all routes. If you want each route's component to have different transitions, you can instead use `<transition>` with different names inside each route component:
+The above usage will apply the same transition for all routes. If you want each route's component to have different transitions, you can instead combine [meta fields](./meta.md) and a dynamic `name` on `<transition>`:
```js
-const Foo = {
- template: `
- <transition name="slide">
- <div class="foo">...</div>
- </transition>
- `
-}
-
-const Bar = {
- template: `
- <transition name="fade">
- <div class="bar">...</div>
- </transition>
- `
-}
+const routes = [
+ { path: '/custom-transition', meta: { transition: 'slide-left' } },
+ { path: '/other-transition', meta: { transition: 'slide-right' } },
+]
+```
+
+```html
+<router-view v-slot="{ Component, route }">
+ <!-- Use any custom transition and fallback to `fade` -->
+ <transition :name="route.meta.transition || 'fade'">
+ <component :is="Component" />
+ </transition>
+</router-view>
```
## Route-Based Dynamic Transition
-It is also possible to determine the transition to use dynamically based on the relationship between the target route and current route:
+It is also possible to determine the transition to use dynamically based on the relationship between the target route and current route. Using a very similar snippet to the one just before:
```html
<!-- use a dynamic transition name -->
-<transition :name="transitionName">
- <router-view></router-view>
-</transition>
+<router-view v-slot="{ Component, route }">
+ <transition :name="route.meta.transition">
+ <component :is="Component" />
+ </transition>
+</router-view>
```
+We can add an [after navigation hook](./navigation-guards.md#global-after-hooks) to dynamically add information to the `meta` field based on the depth of the route
+
```js
-// then, in the parent component,
-// watch the `$route` to determine the transition to use
-watch: {
- '$route' (to, from) {
- const toDepth = to.path.split('/').length
- const fromDepth = from.path.split('/').length
- this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
- }
-}
+router.afterEach((to, from) => {
+ const toDepth = to.path.split('/').length
+ const fromDepth = from.path.split('/').length
+ to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
+})
```
-See full example [here](https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js).
+<!-- TODO: interactive example -->
+<!-- See full example [here](https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js). -->
<button @click="toggleViewName">Toggle view</button>
<Suspense>
<template #default>
- <router-view :name="viewName" v-slot="{ Component }">
+ <router-view :name="viewName" v-slot="{ Component, route }">
<transition
- name="fade"
+ :name="route.meta.transition || 'fade'"
mode="out-in"
@before-enter="flushWaiter"
@before-leave="setupWaiter"
>
<keep-alive>
- <component :is="Component" />
+ <component
+ :is="Component"
+ :key="route.name === 'repeat' ? route.path : undefined"
+ />
</keep-alive>
</transition>
</router-view>
</template>
- <template #fallback>
- Loading...
- </template>
+ <template #fallback> Loading... </template>
</Suspense>
</div>
</template>
}
.fade-enter-active,
.fade-leave-active {
- transition: opacity 0.15s ease;
+ transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
position: absolute;
transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1);
}
+ .slide-left-enter-active,
+ .slide-left-leave-active,
+ .slide-right-enter-active,
+ .slide-right-leave-active {
+ transition: all 0.3s;
+ }
.slide-left-enter-from,
.slide-right-leave-to {
opacity: 0;
import ComponentWithData from './views/ComponentWithData.vue'
import { globalState } from './store'
import { scrollWaiter } from './scrollWaiter'
+import RepeatedParams from './views/RepeatedParams.vue'
let removeRoute: (() => void) | undefined
export const routerHistory = createWebHistory()
{ path: '/long-:n', name: 'long', component: LongView },
{
path: '/lazy',
+ meta: { transition: 'slide-left' },
component: async () => {
await delay(500)
return component()
],
},
{ path: '/with-data', component: ComponentWithData, name: 'WithData' },
- { path: '/rep/:a*', component: component, name: 'repeat' },
+ { path: '/rep/:a*', component: RepeatedParams, name: 'repeat' },
{ path: '/:data(.*)', component: NotFound, name: 'NotFound' },
{
path: '/nested',
next()
})
+router.afterEach((to, from) => {
+ if (to.name === from.name && to.name === 'repeat') {
+ const toDepth = to.path.split('/').length
+ const fromDepth = from.path.split('/').length
+ to.meta.transition = toDepth < fromDepth ? 'slide-right' : 'slide-left'
+ }
+})
+
router.afterEach((to, from) => {
// console.log(
// `After guard: from ${from.fullPath} to ${
--- /dev/null
+<template>
+ <div>
+ <div>Repeated Params</div>
+ <router-link :to="lessNesting">Less nesting</router-link>
+ <br />
+ <router-link :to="moreNesting">More nesting</router-link>
+ <pre>{{ moreNesting }}</pre>
+ <pre>{{ lessNesting }}</pre>
+ </div>
+</template>
+
+<script>
+import { defineComponent, computed } from 'vue'
+import { useRoute } from '../../src'
+
+export default defineComponent({
+ name: 'RepeatedParams',
+
+ setup() {
+ const route = useRoute()
+
+ const lessNesting = computed(() => {
+ const a = [...(route.params.a || [])]
+ a.pop()
+
+ return { params: { a } }
+ })
+
+ const moreNesting = computed(() => {
+ const a = [...(route.params.a || [])]
+ a.push('more')
+
+ return { params: { a } }
+ })
+
+ return { lessNesting, moreNesting }
+ },
+})
+</script>