Vue.js Scroll Progress Indicator

Vue.js Scroll Progress Indicator

Published: 1/2/20213 min read
Vue.js
vuejs.svg logo
SCSS

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 ✌