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 h-screen w-screen -z-10"
      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 h-screen w-screen -z-10" 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>