For new mountains
reload the page

How to Build Scrolling Progress Lines with Vue and SVG

In this tutorial we will be building two SVG lines that keeps the vertical scroll position in a page whenever a user scrolls the page

We’ll use Vue 3, SVG, TailwindCSS, and the awesome @vueuse/core library. And don’t worry - if you’re just getting started with Vue or SVGs, we’ll explain everything as we go.

What do we want to build

The goal is pretty simple: build two vertical lines that has a white background and an orange foreground color that will track the scroll position in the page.

Here’s an example:

See the Pen Untitled by whatupnewyork (@whatupnewyork) on CodePen.

Step 1: Add SVG lines to your document

In your Vue component, start with the following template:

<template>
  <div>
    <svg
      class="fixed top-0 left-0 -z-10 h-screen w-screen"
      xmlns="http://www.w3.org/2000/svg"
    >
      <line
        x1="30"
        y1="0"
        x2="30"
        :y2="windowHeight"
        class="stroke-white"
        stroke-width="2"
      />
      <line
        x1="30"
        y1="0"
        x2="30"
        :y2="redLinePosition"
        class="stroke-orange-500"
        stroke-width="2"
      />
    </svg>
  </div>
</template>

We’re using SVG to draw vertical lines. Let’s break down one of those lines:

<line x1="30" y1="0" x2="30" y2="800" />

So we’re essentially saying: “Draw a white vertical line 2 pixels thick, starting at 30px from the left, stretching from the top down to the bottom.”

We add another <line> element just on top of it with the same coordinates, but with a shorter y2 - this second line is orange and it grows as we scroll.

Step 2: Add Reactive Scroll Logic

Now let’s make the orange line grow as you scroll. Add the following script block:

<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useWindowSize } from "@vueuse/core";

const { width, height: windowHeight } = useWindowSize();
const redLinePosition = ref(0);

onMounted(() => {
  window.addEventListener("scroll", () => {
    const scrollTop = window.scrollY;
    const scrollHeight = document.documentElement.scrollHeight;
    const clientHeight = window.innerHeight;
    const scrollProgress = scrollTop / (scrollHeight - clientHeight);
    const lineY = scrollProgress * clientHeight;
    redLinePosition.value = lineY;
  });
});
</script>

Final results

You now have elegant scroll progress lines on both sides of your page!
As you scroll, the orange part grows. When you resize the window, the lines adjust automatically. All with clean Vue 3 reactivity and zero external animation libraries!

Here is the final component version:

<template>
  <div>
    <svg
      class="fixed top-0 left-0 -z-10 h-screen w-screen"
      xmlns="http://www.w3.org/2000/svg"
    >
      <line
        x1="30"
        y1="0"
        x2="30"
        :y2="windowHeight"
        class="stroke-white"
        stroke-width="2"
      />
      <line
        x1="30"
        y1="0"
        x2="30"
        :y2="redLinePosition"
        class="stroke-orange-500"
        stroke-width="2"
      />
    </svg>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useWindowSize } from "@vueuse/core";

const { width, height: windowHeight } = useWindowSize();
const redLinePosition = ref(0);

onMounted(() => {
  window.addEventListener("scroll", () => {
    const scrollTop = window.scrollY;
    const scrollHeight = document.documentElement.scrollHeight;
    const clientHeight = window.innerHeight;

    const scrollProgress = scrollTop / (scrollHeight - clientHeight);
    const lineY = scrollProgress * clientHeight;

    redLinePosition.value = lineY;
  });
});
</script>