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:
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.