In this post I want to share with you a very minimal Vue.js scroll progress component I’ve created. We will be using Vue 2.x for this demo.
You can check out the GitHub page for the demo here:
👉 https://yossi-abramov.github.io/vue-progress-indicator
And here is a link to the GitHub repository:
👉 https://github.com/yossi-abramov/vue-progress-indicator
There are a couple of ways to implement a scroll progress indicator in your application. In this demo our scroll progress indicator will be in a fixed position, just after a fixed header.
Before diving into the Vue component, let’s go over some of the required styles for our component.
ProgressIndicator.vue template
<template>
<div class="progress-indicator-wrapper">
<div class="progress-indicator"></div>
</div>
</template>
As you can see, the HTML for this component is very simple. We will later add a dynamic width
property for the .progress-indicator
element with Vue’s style binding.
All of the styles for this demo are in @/assets/scss/app.scss
. Here is the relevant portion of SCSS for the component. Of course, you do not have to use SCSS variables, but they are awesome!
// SCSS variables
$header-height: 60px;
$progress-indicator-height: 5px;
$vue-green: #42b983;
…
// Progress Indicator
.progress-indicator-wrapper{
position: fixed;
height: $progress-indicator-height;
background-color: #eee;
width: 100%;
top: $header-height;
.progress-indicator{
height: $progress-indicator-height;
background: $vue-green;
}
}
App.vue
Usually, a scroll progress indicator is a component you would use on many pages of your application. So, for this demo I’ve included the <ProgressIndicator />
in App.vue
:
<template>
<div>
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</div>
<ProgressIndicator />
<div id="app">
<div class="demo-heading">
<span>#</span> Vue.js Scroll Indicator Demo
</div>
<router-view />
</div>
</div>
</template>
<script>
import ProgressIndicator from "@/components/ProgressIndicator";
export default {
components: {
ProgressIndicator
}
};
</script>
<style lang="scss">
@import "@/assets/scss/app.scss";
</style>
Now, let’s head over to @/components/ProgressIndicator.vue
and go over it.
<template>
<div class="progress-indicator-wrapper">
<div class="progress-indicator" :style="{ width: progress }"></div>
</div>
</template>
<script>
export default {
name: "ProgressIndicator",
data() {
return {
progress: "0%"
};
},
methods: {
updateProgressIndicator() {
const { documentElement, body } = document;
let windowScroll = body.scrollTop || documentElement.scrollTop;
let height = documentElement.scrollHeight - documentElement.clientHeight;
this.progress = (windowScroll / height) * 100 + "%";
}
},
mounted() {
window.addEventListener("scroll", this.updateProgressIndicator);
}
};
</script>
First, we need to create a reactive data
property that will be updated on page scroll. Next, in our mounted()
lifecycle method we will add an event listener on window
. The updateProgressIndicator()
method will run on every scroll, bottom or top.
Now, this will work fine, however when you go to a different route the indicator will show the previous route’s progress
state. This happens because our <ProgressIndicator />
component is not rerendered on every route change.
A nice solution would be to call the updateProgressIndicator()
method every time a route change happens. We can watch for route changes with the watch
option. Here is our complete component:
<template>
<div class="progress-indicator-wrapper">
<div class="progress-indicator" :style="{ width: progress }"></div>
</div>
</template>
<script>
export default {
name: "ProgressIndicator",
data() {
return {
progress: "0%"
};
},
watch: {
$route(/* to, from */) {
this.updateProgressIndicator();
}
},
methods: {
updateProgressIndicator() {
const { documentElement, body } = document;
let windowScroll = body.scrollTop || documentElement.scrollTop;
let height = documentElement.scrollHeight - documentElement.clientHeight;
this.progress = (windowScroll / height) * 100 + "%";
}
},
mounted() {
window.addEventListener("scroll", this.updateProgressIndicator);
}
};
</script>
Hope you liked my scroll progress indicator ✌