DEV Community

Yossi Abramov
Yossi Abramov

Posted on • Originally published at yossiabramov.com

Vue.js Scroll Progress Indicator

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#blank

And here is a link to the GitHub repository:

👉 https://github.com/yossi-abramov/vue-progress-indicator#blank

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.

CSS (SCSS) and HTML

<div class="progress-indicator-wrapper">
   <div class="progress-indicator"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

JS

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Hope you liked my scroll progress indicator ✌

✍ For more posts:
https://yossiabramov.com/

Top comments (0)