DEV Community

Abdur Rakib Rony
Abdur Rakib Rony

Posted on

Building a YouTube-Style Loading Bar in Vue 2: A Complete Guide

Have you ever noticed that sleek loading bar at the top of YouTube or Dev.to that appears when you navigate between pages? In this tutorial, we'll build a similar loading bar in Vue 2 using Vuex for state management and Axios for handling HTTP requests. Our implementation will include progress tracking for both uploads and downloads, making it perfect for modern web applications.

What We'll Build
We'll create a global loading bar that:

  • Shows progress for all API requests automatically
  • Tracks both upload and download progress
  • Has smooth animations
  • Can display custom loading text
  • Manages multiple concurrent requests
  • Uses Vuex for state management

Prerequisites

  • Basic knowledge of Vue 2
  • Understanding of Vuex
  • Familiarity with Axios
  • A Vue 2 project with Vuex and Axios installed

Implementation Steps

1. Setting Up the Vuex Store
First, let's create a Vuex module to manage our loading state. Create a new file store/modules/loader.js then import this into your store store/index.js:

const state = {
    pendingRequests: 0,
    isLoading: false,
    loadingText: '',
    progress: 0,
    showPercentage: false
};

const mutations = {
    INCREMENT_PENDING_REQUESTS(state) {
        state.pendingRequests++;
        state.isLoading = true;
    },
    DECREMENT_PENDING_REQUESTS(state) {
        state.pendingRequests--;
        state.isLoading = state.pendingRequests > 0;
        if (!state.isLoading) {
            state.progress = 0;
            state.loadingText = '';
            state.showPercentage = false;
        }
    },
    SET_LOADING_TEXT(state, text) {
        state.loadingText = text;
    },
    SET_PROGRESS(state, progress) {
        state.progress = Math.min(Math.max(progress, 0), 100);
        state.showPercentage = true;
    }
};

const getters = {
    isLoading: state => state.isLoading,
    loadingText: state => state.loadingText,
    progress: state => state.progress,
    showPercentage: state => state.showPercentage
};

export default {
    namespaced: true,
    state,
    mutations,
    getters
};
Enter fullscreen mode Exit fullscreen mode

2. Creating the Loading Bar Component
Create a new component components/GlobalLoader.vue:

<template>
  <div v-show="isLoading" class="progress-container">
    <div 
      class="progress-bar"
      :style="{ 
        transform: `scaleX(${progress / 100})`,
        opacity: progress === 100 ? 0 : 1
      }"
    ></div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'GlobalLoader',

  computed: {
    ...mapGetters('loader', [
      'isLoading',
      'progress',
      'showPercentage'
    ])
  }
}
</script>

<style scoped>
.progress-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  z-index: 9999;
  background-color: rgba(255, 255, 255, 0.1);
}

.progress-bar {
  width: 100%;
  height: 100%;
  background-color: #16bb93;
  transform-origin: left;
  transition: transform 0.2s ease, opacity 0.3s ease;
}
</style>
Enter fullscreen mode Exit fullscreen mode

3. Creating an Axios Instance with Progress Tracking
Create a new file for your HTTP client configuration:

import axios from 'axios';
import store from '@/store';

const apiBaseUrl = process.env.VUE_APP_NODE_ENV === 'development'
  ? process.env.VUE_APP_LOCAL_BASE_URL
  : process.env.VUE_APP_BASE_URL;

export default ({ loadingText = '', trackProgress = false } = {}) => {
  const instance = axios.create({
    baseURL: apiBaseUrl,
    headers: {
      Accept: '*/*',
      Authorization: `Bearer ${store.state.authentication.token}`,
      locale: 'en',
    },
  });

  // Request interceptor
  instance.interceptors.request.use(
    config => {
      store.commit('loader/INCREMENT_PENDING_REQUESTS');
      if (loadingText) {
        store.commit('loader/SET_LOADING_TEXT', loadingText);
      }

      if (trackProgress) {
        // Initialize progress
        store.commit('loader/SET_PROGRESS', 0);

        // Track upload progress
        config.onUploadProgress = progressEvent => {
          if (progressEvent.total) {
            const percentCompleted = Math.round(
              (progressEvent.loaded * 50) / progressEvent.total
            );
            store.commit('loader/SET_PROGRESS', percentCompleted);
          }
        };

        // Track download progress
        config.onDownloadProgress = progressEvent => {
          if (progressEvent.total) {
            const percentCompleted = Math.round(
              50 + (progressEvent.loaded * 50) / progressEvent.total
            );
            store.commit('loader/SET_PROGRESS', percentCompleted);
          }
        };
      }

      return config;
    },
    error => {
      store.commit('loader/DECREMENT_PENDING_REQUESTS');
      return Promise.reject(error);
    }
  );

  // Response interceptor
  instance.interceptors.response.use(
    response => {
      if (trackProgress) {
        store.commit('loader/SET_PROGRESS', 100);
      }
      store.commit('loader/DECREMENT_PENDING_REQUESTS');
      return response;
    },
    error => {
      store.commit('loader/DECREMENT_PENDING_REQUESTS');
      return Promise.reject(error);
    }
  );

  return instance;
};
Enter fullscreen mode Exit fullscreen mode

How It Works

State Management: The Vuex module tracks:

  • Number of pending requests
  • Loading state
  • Progress percentage
  • Custom loading text

Progress Tracking: We split the progress bar into two phases:

  • First 50% for upload progress
  • Last 50% for download progress

Request Handling:

  • The request interceptor increments the pending requests counter
  • The response interceptor decrements it
  • When all requests complete, the loading bar disappears

Smooth Animations:

  • CSS transitions create smooth progress updates
  • The bar fades out when reaching 100%
  • Transform-origin ensures the bar grows from left to right

Usage Example
Here's how to use the loading bar in your API calls:

async login({ commit }, parameter) {
  try {
    const response = await HTTP({
      trackProgress: true,
      loadingText: 'Processing...'
    }).post('/auth/login', {
      email: parameter.email,
      password: parameter.password,
    });

    // Handle response...
  } catch (error) {
    // Handle error...
  }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices and Tips

  • The loader handles multiple concurrent requests gracefully by tracking the number of pending requests.
  • Use v-show instead of v-if to prevent frequent DOM updates.
  • Split the progress bar into upload and download phases for better user feedback.

Conclusion
This implementation provides a professional, YouTube-style loading experience for your Vue 2 application. It's highly customizable and can be easily integrated into any Vue project using Vuex and Axios.

Top comments (0)