Vue.js Scroll Progress Indicator
Tags: vue, javascript
Published: 2021-01-03 07:36:08

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](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#blank](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.

### CSS (SCSS) and HTML

```xml
<div class="progress-indicator-wrapper">
   <div class="progress-indicator"></div>
</div>
```

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`](https://vuejs.org/v2/guide/class-and-style.html).

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
// 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;
    }
}
```

### 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`:

```javascript
<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.

```javascript
<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:

```javascript
<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 ✌

✍ For more posts:
[https://yossiabramov.com/](https://yossiabramov.com/)