Scroll to Top in Vue with Reusable Components

In improving the user experience on any website with a lot of content on its web pages, it is common for frontend developers to implement a scroll-to-top functionality through a button, to make things more enjoyable and convenient for users.

While users can use the command + up arrow on a keyboard, it is also important to bear in mind that potentially more than half of your users might be using mobile devices and won't be using a keyboard connected to their phone while scrolling through your website. Smaller screens often times also necessitate more scrolling, making navigation from bottom-to-top laborious.

In this guide, we'll implement a scroll-to-top button using Vue 3 - step by step. Alongside the reusable component, we'll be building a news app that provides breaking news from various sources with the News API.

Source Code: As usual, you can tinker with the source code hosted on GitHub.

Project Setup

Getting started with the Vue.JS framework is as simple as including a JavaScript file in an HTML file. But for a real-world and large-scale application, the vue-cli is the best way to get started! We’ll use the vue-cli in our tutorial today.

Advice: If you're not already acquainted with vue-cli - read our "Guide to Getting Started with the Vue CLI"!

Let’s create the project and name it vue-scroll-to-top, using the command below:

$ vue create vue-scroll-to-top

This creates a template to start building our project. You can start the application by moving into the project:

$ cd vue-scroll-to-top

And serve it with Yarn or NPM:

$ yarn serve

Or:

$ npm run serve

On localhost, at port 8080 - your application will be served:

Building the Components

With the template spun up - we can start building the reusable scroll-to-top component, which generalizes to any app you'd like to build. Before defining the component, let's prepare and clean up the App.vue starter we've created with Vue CLI.

Cleaning the Vue Project

Under src/App.vue, we'll register the soon-to-be component as AppButton:

<template>
    <section class="app-container">
        <AppButton />
    </section>
</template>

<script>
import AppButton from "./components/AppButton.vue";

export default {
    name: "App",

    components: {
        AppButton
    },

    setup() {
        return {};
    }
};
</script>

<style>
@import url("https://fonts.googleapis.com/css2?family=Nunito&display=swap");
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font: inherit;
}
html {
    font-family: "Nunito", sans-serif;
}
body {
    font-size: 15px;
    color: #000000;
    background: #fff;
}
h1,
h2,
h3,
h4,
h5,
h6 {
    color: #000000;
}
img {
    max-width: 100%;
}
ul {
    list-style: none;
}
a {
    text-decoration: none;
    display: inline-block;
}
.app-container {
    max-width: 82rem;
    margin: auto;
    padding: 3rem 1rem;
}
</style>

Building the Button UI

With the page that'll display the button (and news) - we can start building the components. Let's start with the button!

In the components folder, create an AppButton.vue file. In the button, we'll include an icon that signifies the up arrow to visually convey the functionality. For the icon itself, we'll use an icon library called Font Awesome which has over 19.000 icons over 6 styles and brands, though this can be substituted with a simple caret sign (^), a custom icon or an icon from other libraries if you don't want to introduce another dependency.

Using your preferred dependency manager, install the core package that contains all the utilities to make the icons work:

$ yarn add @fortawesome/fontawesome-svg-core
# Or
$ npm i --save @fortawesome/fontawesome-svg-core

Next, we'll install the free solid SVG icons (one of the multiple available "sets" or styles), using the following command:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

$ yarn add @fortawesome/free-solid-svg-icons
# Or
$ npm i --save @fortawesome/free-solid-svg-icons

And lastly, we'll install the Font Awesome Vue Component for Vue 3, using the following command:

$ yarn add @fortawesome/vue-fontawesome@latest-3
# Or
$ npm i --save @fortawesome/vue-fontawesome@latest-3

Before we can render our icon in our button, we need to import the installed Font Awesome dependencies into our project, including the specific icon name we want to use. Let's update the main.js file:

import { createApp } from 'vue'
import App from './App.vue'

/* Import the fontawesome core */
import { library } from '@fortawesome/fontawesome-svg-core'
/* Import font awesome icon component */
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
/* Import specific icons */
import { faArrowUp } from '@fortawesome/free-solid-svg-icons'
/* Add icons to the library */
library.add(faArrowUp)

createApp(App).component('font-awesome-icon', FontAwesomeIcon).mount('#app')

We can now call the arrow-up icon, as faArrowUp, in AppButton.vue to render it in the UI! We can also style the button from the component file:

<template>
  <button class="app-button">
    <font-awesome-icon icon="fa-solid fa-arrow-up" />
  </button>
</template>

<style scoped>
.app-button {
  border-radius: 50%;
  height: 50px;
  width: 50px;
  padding: 4px;
  box-shadow: 2px 2px #e9e9e9;
  cursor: pointer;
  position: fixed;
  right: 40px;
  bottom: 40px;
}
</style>

On the DOM, our scroll-to-top button should look like this, when you open your application on localhost:8080:

Building the News Feeds UI

To create an environment in which a user might want to use the button, rather than manually scrolling - let's fetch lots of news content from the free News API. For making fetch requests, we'll take advantage of the axios package.

If you don't have it installed already, you can do so via Yarn or NPM:

$ yarn add axios
# Or
$ npm install axios

Advice: If you haven't used Axios before and would like to get a hang of working with it - read our "Guide to Making Asynchronous HTTP Requests in JavaScript with Axios"!

To use the News API, you'll need to register an account - to gain an API key that is free for development environments. For this demo, we will keep things simple, by just fetching all articles about, say, Bitcoin. In the components folder, let's create a new file called NewsList.vue. In NewsList.vue, we will build out a list of Bitcoin news articles which we would get as a response from the API.

NewsList.vue should now include the following codes:

<template>
  <section>
    <ul class="news-list">
      <li class="news-card" v-for="newsItem in newsList" :key="newsItem.id">
        <p><span class="heading">Title</span>: {{ newsItem.title }}</p>
        <p><span class="heading">Author</span>: {{ newsItem.author }}</p>
        <p>
          <span class="heading">Description</span>: {{ newsItem.description }}
        </p>
        <p><span class="heading">Source</span>: {{ newsItem.source.name }}</p>
      </li>
    </ul>
  </section>
</template>

<script>
import axios from "axios";
import { onMounted, ref } from "vue";

export default {
  name: "NewsList",
  setup() {
    const newsList = ref([]);

    const fetchNews = () => {
      axios
        .get(
          "https://newsapi.org/v2/everything?q=bitcoin&apiKey=94585b39692e4ea6b372bea15abf7dcc"
        )
        .then((response) => (newsList.value = response.data.articles));
    };

    onMounted(() => {
      fetchNews();
    });

    return { newsList };
  },
};
</script>

<style scoped>
ul.news-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3rem;
}
ul li.news-card {
  padding: 10px;
  display: flex;
  border-radius: 8px;
  flex-direction: column;
  row-gap: 5px;
  box-shadow: 0px 4px 12px -1px rgba(120,116,120,0.79);
}
li p span.heading {
  font-weight: 600;
  font-size: 18;
}
</style>

Then, we make sure to update App.vue, so that the list of news articles can be displayed:

<template>
  <section class="app-container">
    <AppButton />
    <NewsList />
  </section>
</template>

<script>
import AppButton from "./components/AppButton.vue";
import NewsList from "./components/NewsList.vue";
export default {
  name: "App",
  components: {
    AppButton,
    NewsList,
  },
  setup() {
    return{}
  }
};
</script>

<style>
@import url("https://fonts.googleapis.com/css2?family=Nunito&display=swap");
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font: inherit;
}
html {
  font-family: "Nunito", sans-serif;
}
body {
  font-size: 15px;
  color: #000000;
  background: #fff;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  color: #000000;
}
img {
  max-width: 100%;
}
ul {
  list-style: none;
}
a {
  text-decoration: none;
  display: inline-block;
}
.app-container {
  max-width: 82rem;
  margin: auto;
  padding: 3rem 1rem;
}
</style>

Usually - you'd want to paginate results, instead of having them pile up. However, we're specifically creating a case in which a scroll-to-top is useful.

Show/Hide Scroll to Top Button Using Scroll Listener

As part of an engaging experience for the user, let’s make sure that the scroll-to-top button is revealed only when the user starts to scroll down the page.

To achieve this, we have to listen to the scroll event at the point a user scrolls down the page.

First, let’s target the button element by setting a ref to it. Then, we make use of the beforeUnmount() lifecycle hooks to add and remove the button from the DOM, with the help of the scroll listener - upon scrolling down the page or to the topmost of the page. We can also check if a user is at the top of the page if window.scrollY is greater than 0. With this, we can toggle the visibility of the button, where need be.

Let's update the AppButton.vue component to display or hide the button based on how far the user has scrolled:

<template>
  <button ref="appButton" class="app-button">
    <font-awesome-icon icon="fa-solid fa-arrow-up" />
  </button>
</template>

<script>
import { onMounted, ref, onBeforeMount } from "vue";
export default {
  name: "AppButton",
  setup() {
    const appButton = ref();
    const userScroll = () => {
      if (window.scrollY > 0) {
        appButton.value.classList.add("showButton");
        console.log('scrolled');
      } else {
        appButton.value.classList.remove("showButton");
        console.log('top');
      }
    };
    onMounted(() => {
      window.addEventListener("scroll", userScroll);
    });
    onBeforeMount(() => {
      window.removeEventListener("scroll", userScroll);
    });
    return { appButton };
  },
};
</script>

<style scoped>
.app-button {
  border-radius: 50%;
  height: 50px;
  width: 50px;
  padding: 4px;
  box-shadow: 2px 2px #e9e9e9;
  cursor: pointer;
  position: fixed;
  bottom: 40px;
  display: none;
  right: 40px;
}

.showButton {
  display: block;
}
</style>

Implementing Scroll Event

What’s left for us at this point is to make the button actually scroll to the top of the page when a user clicks it. To achieve this, let’s create a scrollToTop() method in AppButton.vue. This is a method that we would invoke when window.scrollY = 0. Now, when the scrollToTop() method is called, our page smoothly scrolls to the topmost portion, and the scroll-top-top button disappears too:

// AppButton.vue
const scrollToTop = () => {
  window.scrollTo({ top: 0, behavior: "smooth" });
};

When this snippet of code is added to AppButton.vue, you’ll find that we have successfully implemented a scroll-to-top functionality in our Vue app that we can plug into any project where we need the same.

Conclusion

Congratulations on following along till the very end! 🔥

In this tutorial, we looked at how to build scroll-to-top functionality into a Vue application. This is a reusable generic component and can easily be introduced in other projects as well.

Relevant Resources

Last Updated: April 17th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Uchechukwu AzubukoAuthor

Uchechukwu Azubuko is a Software Engineer and STEM Educator passionate about making things that propel sustainable impact and an advocate for women in tech.

He enjoys having specific pursuits and teaching people better ways to live and work - to give them the confidence to succeed and the curiosity required to make the most of life.

Project

React State Management with Redux and Redux-Toolkit

# javascript# React

Coordinating state and keeping components in sync can be tricky. If components rely on the same data but do not communicate with each other when...

David Landup
Uchechukwu Azubuko
Details

Getting Started with AWS in Node.js

Build the foundation you'll need to provision, deploy, and run Node.js applications in the AWS cloud. Learn Lambda, EC2, S3, SQS, and more!

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms