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>
content_copyVue
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; } }
content_copySCSS
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>
content_copyVue
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>
content_copyVue
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>
content_copyVue
Hope you liked my scroll progress indicator ✌