DEV Community

Cover image for How to add animated loading transitions to your Vue.js app with a dynamic component
tq-bit
tq-bit

Posted on • Originally published at blog.q-bit.me

How to add animated loading transitions to your Vue.js app with a dynamic component

Transitions in Vue are great. As a built-in feature, they allow developers to incorporate smooth navigation animations quickly. In this article, we'll take it even further and build a dedicated loading transition using an intermediate component

TL:DR - check out this codesandbox for the article's source code

Getting started

Let's fire up your favorite editor's terminal and create a new Vue project using vue@latest:

npm create vue@latest .

Enter fullscreen mode Exit fullscreen mode

Select Router as an additional dependency during setup and run npm run install after the project has been scaffolded. Finally, get rid of all views and components created by the script, and you're ready to start.

Boilerplate

Let's add some simple views we can navigate between next. Inside the views folder, create these three components:

  • HomeView.vue
  • AboutView.vue
  • ShopView.vue

Add the following code to each and adjust the title, as well as the paragraph:

<script setup>
</script>

<template>
  <main>
    <h1>This is the home / about / shop page</h1>
    <p>This is the home / about / shop page</p>
  </main>
</template>

Enter fullscreen mode Exit fullscreen mode

The App.vue file is next. Let's add some basic structure, styles, and a placeholder for our transition logic.

<script setup></script>

<template>
  <nav>
    <RouterLink to="/">Home</RouterLink>
    <RouterLink to="/about">About</RouterLink>
    <RouterLink to="/shop">Shop</RouterLink>
  </nav>

  <transition name="fade">
    <Loading v-if="loading"></Loading>
  </transition>
  <main>
    <RouterView />
  </main>
</template>

<style></style>

Enter fullscreen mode Exit fullscreen mode

Styles:

body {
  background-color: #222;
  color: #eee;
  padding: 1rem;
  overflow-x: hidden;
}

nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
}

nav a {
  color: #eee;
  text-decoration: none;
  font-weight: bold;
  font-size: 1.2rem;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

nav a:hover,
nav a.router-link-active {
  background-color: steelblue;
}

main {
  margin-top: 2rem;
  text-align: center;
}

Enter fullscreen mode Exit fullscreen mode

Finally, let's adjust the router/index.js file to incorporate all relevant views.

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue'),
    },

    {
      path: '/shop',
      name: 'shop',
      component: () => import('../views/ShopView.vue'),
    },
  ],

})

export default router


Enter fullscreen mode Exit fullscreen mode

Now that all preparations are in place, let's start implementing the transition component

Adding the loading component

The component we're about to build will slide in from the right whenever we change the route and slide out after a specified time. To keep it simple, we'll animate the Vue logo from the boilerplate. It will look like this:

Let's start with the template code.

  • Create a new file in the components directory and name it Loading.vue.
  • Add an image with two filters.
  • The filter's values depend on the current loading percentage
  • Below, is a simple text to indicate the component's purpose
<script setup></script>

<template>
    <div class="loading-container">
        <img :style="`filter: contrast(${loadingPercentage / 100}) blur(${12.5 - loadingPercentage/8}px)`"  height="240px" width="240px" src="@/assets/logo.svg" alt="logo">
        <h2>More content is on the way, stand by</h2>
    </div>
</template>

<style scoped></style>

Enter fullscreen mode Exit fullscreen mode
  • We'll have to add the variable of loading percentage next
  • Also, two handlers are needed to set the value dynamically once the component is loaded and unloaded
  • Let's add them to the script section
import { ref, onMounted, onUnmounted } from 'vue';

const loadingPercentage = ref(0);

onMounted(() => {
    const interval = setInterval(() => {
        loadingPercentage.value += 2.5;
        if (loadingPercentage.value >= 100) {
            clearInterval(interval);
        }
    }, 40);
})

onUnmounted(() => {
    loadingPercentage.value = 0
})

Enter fullscreen mode Exit fullscreen mode
  • Let's wrap up this section by adding some styles
  • Especially the position: absolute is important to keep the UI clearly arranged
  • Add the loading-container class to the styles section
.loading-container {
    background-color: #0a0a0a;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 0.5rem;
    position: absolute;
    top: 0;
    left: 0;
}

Enter fullscreen mode Exit fullscreen mode

Finalize the App.vue file

All that's left to do is add the loading logic and the transition itself to the App.vue file.

  • We need to import the useRouter composable and add a function to the beforeEach router lifecycle hook
  • Doing this, however, runs the transition when the component is initially rendered, which happens whenever a user opens the app in their browser
  • To prevent this from happening, we'll add an indicator variable that prevents the initial rendering altogether

Note that you do not have to set the loading values statically, you can easily bind other values or even network composables into the lifecycle hook, as well as into the component itself using properties

Add the following to the App.vue's script section:

import { ref } from 'vue';
import { RouterLink, RouterView, useRouter } from 'vue-router';
import Loading from './components/Loading.vue'

const loading = ref(false);
const initiallyLoaded = ref(false);

useRouter().beforeEach(() => {
  if (initiallyLoaded.value === false) {
    initiallyLoaded.value = true;
    return;
  }

  loading.value = true
  setTimeout(() => {
    loading.value = false
  }, 2500)
})

Enter fullscreen mode Exit fullscreen mode

All that's left to do now is to add the transition CSS code to create a smooth sliding transition.

.fade-enter-active,
.fade-leave-active {
  transition: all 0.75s ease;
}

.fade-enter-from {
  transform: translateX(100vw);
  opacity: 0;
}

.fade-leave-to {
  transform: translateX(-100vw);
  opacity: 0;
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)