DEV Community

Cover image for How to create a scroll to top button with vanilla JS & CSS
Amir-Lotfi
Amir-Lotfi

Posted on

How to create a scroll to top button with vanilla JS & CSS

In this tutorial, you will learn in a few short steps how to create a scroll to top button with CSS inset shorthand and vanilla JavaScript.

Scroll to top button can be very helpful for websites with lots of content, pages with infinite scrolling, or mobile devices with a small screen that can cause the content scroll to extend.

You can follow along with this tutorial by forking this template.

Step one, create the button

To create a scroll button, use an anchor tag with href="#" this makes the browser return to the top of the page when clicked, or you can use a custom Id to return to a specific part of the page.

<a href="#">scroll-to-top</a>

Step two, position and style the button

To make the button position fixed relative to the viewport, you need to set position: fixed on the anchor tag. When the element position is fixed, it gets removed from the normal document flow and then positioned with top, right, bottom, and left properties relative to the viewport.

Although, there is a shorthand for positioning properties called inset.
Inset works just like margin shorthand, which is used to set margin-top, margin-right, margin-bottom, and margin-left all in one.

syntax

inset: top right bottom left

When auto is used as a value for inset, it considers that value omitted.
Therefore you can use inset like the one below to position your button in the bottom right corner of the viewport.

inset: auto 2em 2em auto;

To put things together, add a class scrollToTopBtn to the anchor tag and style your button like the one below.

.scrollToTopBtn {
  color: #f2f2f2;
  background-color: #151515;
  text-decoration: none;
  border-radius: 25px;
  position: fixed;
  outline: none;
  z-index: 100;
  padding: 0.75em 1.5em;
  inset: auto 2em 2em auto;
}
Enter fullscreen mode Exit fullscreen mode

Step three, make the button responsive

Now the scroll to top button is styled, placed correctly, and it works. But there is a problem, the button always is visible. To fix that you need to use JavaScript to hide and show the button according to page scroll.

To do that, first, get the button and store it in a variable.

const scrollToTopBtn = document.querySelector(".scrollToTopBtn");

Then get the root element of the document for the offset values.

const rootElement = document.documentElement;

Next, you should register an event listener on scroll event to calculate the button visibility status.

const handleScroll = () => {}
document.addEventListener("scroll", handleScroll);
Enter fullscreen mode Exit fullscreen mode

The handleScroll function will be called every time the user scrolls.

After that, you need the total number of pixels that can be scrolled, and to get that inside the handleScroll function, you need to subtract scrollHeight by clientHeight to get the total amount of pixels that can get scrolled.

const scrollTotal = rootElement.scrollHeight - rootElement.clientHeight;

Now that you have the maximum number of pixels that can be scrolled, you need to divide it by the amount the page has been scrolled to get the scrolled ratio between 0 and 1. Using scroll ratio you can condition the location that you want to hide and show the button. The closer you get to 1, the more the user has to scroll before seeing the button.

if ((rootElement.scrollTop / scrollTotal) > 0.25) {
    // Show the button
    scrollToTopBtn.classList.add("isVisible")
} else {
    // Hide the button
    scrollToTopBtn.classList.remove("isVisible")
}
Enter fullscreen mode Exit fullscreen mode

Finally, to make it work, you first need to add opacity: 0; to the scrollToTopBtn class to hide the button on page load. Then add class isVisible with opacity: 1; to the button when the page scroll passes the ratio you chose. Last but not least, add transition: all 250ms ease-in-out; to scrollToTopBtn class for animating the button.

.scrollToTopBtn {
  color: #f2f2f2;
  background-color: #151515;
  text-decoration: none;
  border-radius: 25px;
  position: fixed;
  outline: none;
  z-index: 100;
  opacity: 0;
  padding: 0.75em 1.5em;
  inset: auto 2em 2em auto;
  transition: all 250ms ease-in-out;
}

.isVisible {
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

Step four, fix unwanted clicks

When scroll to top button is hidden, it still can be clicked, which is not supposed to happen. To fix that problem, add pointer-events: none; to class scrollToTopBtn to ignore the click event and add pointer-events: auto; to class isVisible to bring back the click event to the button when it's visible.

.scrollToTopBtn {
  color: #f2f2f2;
  background-color: #151515;
  text-decoration: none;
  border-radius: 25px;
  position: fixed;
  outline: none;
  z-index: 100;
  opacity: 0;
  pointer-events: none;
  padding: 0.75em 1.5em;
  inset: auto 2em 2em auto;
  transition: all 250ms ease-in-out;
}

.isVisible {
  pointer-events: auto;
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

Step five, add smooth scroll to the page

Now scroll to top button works perfectly still, you can add a nice touch to your website with smooth scroll. To do that, just add scroll-behavior: smooth; to the html tag.

html {
  scroll-behavior: smooth;
}
Enter fullscreen mode Exit fullscreen mode

To conclude

Scroll to top button is a simple yet useful feature that can improve user experience of your website drastically. In this tutorial, I showed you how to build scroll to top button with just a few lines of code without any library. You can see and play with the final code at NexusCode.online

Top comments (9)

Collapse
 
lexlohr profile image
Alex Lohr

Nice one. An interesting yet mostly unknown property of classList is that classList.toggle accepts a second argument to force a toggle state, so your code can be reduced:

// Before
if ((rootElement.scrollTop / scrollTotal) > 0.25) {
    // Show the button
    scrollToTopBtn.classList.add("isVisible")
} else {
    // Hide the button
    scrollToTopBtn.classList.remove("isVisible")
}

// After
scrollToTopBtn.classList.toggle(
  "isVisible",
  (rootElement.scrollTop / scrollTotal) > 0.25
)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
robole profile image
Rob OLeary

To improve the performance, you can consider debouncing the scroll handler also.

In your code, the handler function is called every time the user scrolls the page by a few pixels. This could mean the handler function is called many times per second if you scroll quickly through the page, which is not necessary.

I speak about using debouncing in the following post, if you would like to learn more:

Collapse
 
thomasbnt profile image
Thomas Bnt ☕

Hello ! Don't hesitate to use colors in

console.log('code blocks')
Enter fullscreen mode Exit fullscreen mode

Example of using Codeblock md

Collapse
 
frankwisniewski profile image
Frank Wisniewski • Edited

A few years ago I realized it as a plugin in vanilla.js

insert the following line before the closing body tag:

<script data-color=blue src="scrolltotop.js"></script>

The file scrolltotop.js:

(function(){
  const scrollScript = document.currentScript,
        scrollButtonColor = scrollScript.dataset.color || 'red',
        fhwScrollerDiv = document.createElement( 'div' ),
        scrollMe = () => {
          if ( document.querySelector( 'body' ).scrollIntoView ){
            document.querySelector( 'body' )
              .scrollIntoView( { behavior: 'smooth' } )
          } else {
          //Polyfill for scrollIntoView....
            let diff = document.documentElement.scrollTop || document.body.scrollTop;
            if (diff > 0) {
              window.requestAnimationFrame(scrollMe);
              window.scrollTo(0, diff - diff / 6);
            }
          }
        }

  fhwScrollerDiv.innerHTML = `
    <svg width="50" height="50" viewbox="0 0 100 100">
      <circle  
        fill="${scrollButtonColor}" 
        cx="50" 
        cy="50" 
        r="50" 
      />
      <path 
        stroke="white" 
        stroke-width="16" 
        stroke-linecap="round" 
        d="M50 80 L50 20 M50 20 L80 50 M50 20 L20 50"
      />
      </svg>`;

  fhwScrollerDiv.style.cssText=`
    z-index:1000;
    position:fixed;
    bottom:20px;
    right:10px;
    cursor:pointer;
    display:none;`

  document.body.appendChild(fhwScrollerDiv);

  window.onscroll = function() {
      if (this.pageYOffset > 200) {
        fhwScrollerDiv.style.opacity = "0.5";
        fhwScrollerDiv.style.display = "block";
      } else {
        fhwScrollerDiv.style.opacity = "0";
        fhwScrollerDiv.style.display = "none";
      }
  }

  fhwScrollerDiv.querySelector('svg')
    .onmouseover = () => 
      fhwScrollerDiv.style.opacity = "1"

  fhwScrollerDiv.querySelector('svg')
    .onmouseout = () => 
      fhwScrollerDiv.style.opacity = "0.5"

  fhwScrollerDiv.onclick = scrollMe;  
})();  
Enter fullscreen mode Exit fullscreen mode
Collapse
 
link2twenty profile image
Andrew Bone

A little trick you can do to remove the overhead of a scroll listener is make trigger line and use IntersectionObserver to see if it's on the screen or not.

Something like this

Collapse
 
sojinsamuel profile image
Sojin Samuel

bravo amir

Collapse
 
amirlotfi profile image
Amir-Lotfi

Thank you

Collapse
 
longfei347 profile image
longfei347

good

Collapse
 
amirlotfi profile image
Amir-Lotfi

Thank you