vue.jsvue-routervue-composition-apivue-transitions

Vue route transitions only works on load or refresh, but no animations on routing


I added a transition on a suspense component as the Vue's docs suggested way of doing it to route to a dynamic route path, the animations works when loading and reloading each page individually but it doesn't work on routing (when users click on a routerlink), also there are no errors shown.

to see how this behaves you can take a look at the live site: https://rest-countries-api.onrender.com/

and to see all the code you can go to : https://github.com/anas-cd/rest_api/tree/dev/rest_api

here is the main App.vue file:

<template>
    <NavigationBar />
    <RouterView v-slot="{ Component }">
        <template v-if="Component">
            <Transition name="scale" mode="out-in">
                <suspense>
                    <template #default>
                        <div class="wl">
                            <component :is="Component"></component>
                        </div>
                    </template>
                    <template #fallback>Loading..</template>
                </suspense>
            </Transition>
        </template>
    </RouterView>
</template>

<script setup>
import NavigationBar from './components/NavigationBar.vue';
</script>

<style lang="scss">
// other styling 

// transition styling
.scale-enter-active,
.scale-leave-active {
    transition: all 0.5s ease;
}

.scale-enter-from,
.scale-leave-to {
    opacity: 0;
    transform: scale(0.8);
}
</style>

also here is a component for routing placed on the home page (simplified for context):

<template>
    <router-link :to="countryCode">
        <div class="card" :key="$route.params.code">
                <h2>{{ countryName }}</h2>
        </div>
    </router-link>
</template>

<script setup>
// imports
import { ref } from 'vue';

// data
const props = defineProps({
    countryData: {
        type: Object,
        required: true,
    },
});

const countryName = ref(props.countryData.name.common);
</script>

<style scoped lang="scss">
// styling ....
</style>

lastly here is the destination view :

<template>
    <RouterLink to="/" class="backLink">
        <img src="@/assets/arrow-back-outline.svg" alt="go back button" />
        <button>Back</button>
    </RouterLink>
    <div class="country" :key="$route.params.code">
       name
    </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
import CountriesApi from '@/services/CountriesApi';


const route = useRoute();
// data
const countryData = ref(
    await CountriesApi.getCountryByCode(route.params.code).catch(() => {
        return null;
    })
);

const name = ref('');

name.value = countryData.value[0].name.common;

watchEffect(async () => {
 
        countryData.value = await CountriesApi.getCountryByCode(
            route.params.code
        
        name.value = countryData.value[0].name.common;
    
});
</script>

Solution

  • for future reference, after many tries, the problem was that <Transition> only accepted one node as a child in both origin and destination views/components to apply its transition animations on that root node.

    so here are the required changes

    1

    App.vue: remove the <div> (or other HTML elements) wrapping container for <component>, and instead add it in your components/views acting as a root node wrapping everything there.

    .
    .
    .
    <Transition name="scale" mode="out-in">
      <suspense>
        <template #default>
           <!-- <div class="wl">  delete this -->
             <component :is="Component"></component>
           <!-- </div> --> 
          </template>
        <template #fallback>Loading..</template>
      </suspense>
    </Transition>
    .
    .
    .
    

    2

    all views and components: you need to add a container root node, <div> or whatever works with your app for <Transition> to apply its animation effects on.

    <template>
    <!-- adding the root node as <div class="wl"></div> container -->
       <div class="wl"> 
         <RouterLink to="/" class="backLink">
             <img src="@/assets/arrow-back-outline.svg" alt="go back button" />
             <button>Back</button>
         </RouterLink>
         <div class="country" :key="$route.params.code">
            name
         </div>
       </div>
    </template>
    

    Simply it didn't work before because the destination view got two root elements <RouterLink> and <div class="country">, thus <Transition> doesn't have a root node to apply the animations on.

    this usually gives an error or warning but for some reason, it didn't in my case.