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
};
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>
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;
};
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...
}
}
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)