DEV Community

loading...
Cover image for React Component to Smooth Scroll to the Top

React Component to Smooth Scroll to the Top

prnvbirajdar profile image Pranav Birajdar Updated on ・2 min read

It's kinda rare to find a smooth scrolling button that takes you to the top of the page on modern blogging websites, especially the ones that are a long 15 minute read!

However, whenever I come across one, I always tend to use it and appreciate the elegance of this simple button that has such a specific job.

After perusing Stack Overflow and GitHub for solution, I came across an elegant React component that uses Hooks and wanted to share it with this community!

Our button should function like this:
Alt Text

These are following test-cases for our component:

  • Button should always be at the right bottom of the page
  • Button should be hidden and should only appear when we scroll for a certain height
  • On clicking it, we should be smoothly taken to the top of the page

The Hook component achieves the following functionality.

import React, { useEffect, useState } from "react";

export default function ScrollToTop() {
  const [isVisible, setIsVisible] = useState(false);

  // Top: 0 takes us all the way back to the top of the page
  // Behavior: smooth keeps it smooth!
  const scrollToTop = () => {
    window.scrollTo({
      top: 0,
      behavior: "smooth"
    });
  };

  useEffect(() => {
    // Button is displayed after scrolling for 500 pixels
    const toggleVisibility = () => {
      if (window.pageYOffset > 500) {
        setIsVisible(true);
      } else {
        setIsVisible(false);
      }
    };

    window.addEventListener("scroll", toggleVisibility);

    return () => window.removeEventListener("scroll", toggleVisibility);
  }, []);

//scroll-to-top classes: fixed, bottom:0, right:0
  return (
    <div className="scroll-to-top">
      {isVisible && (
        <div onClick={scrollToTop}>
          <h3>Go up!</h3>
        </div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We're almost done! Just import this component in your react file and stick it at the very end.

And voila, it should work!

Here is a basic, quick, and ugly demo of how it should function!

Discussion (24)

pic
Editor guide
Collapse
darkwiiplayer profile image
DarkWiiPlayer

I had to build a similar thing a while ago, but without any framework; ended up with much less code by simply adding a scroll event handler that toggles a top class on the body element. The CSS then just has a rule like .top .scroll-to-top { opacity: 1 }

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

Damn! That's awesome. Do you have a demo by any chance?

Collapse
darkwiiplayer profile image
DarkWiiPlayer

cc-entrance.comcard.de Ignore the fact that it's in German 😁

Also there's a few additional effects other than just fading in and out, which obviously makes the CSS a bit more complex.

Thread Thread
prnvbirajdar profile image
Pranav Birajdar Author • Edited

This looks so good! I just looked at the event code and the underlying function is almost the same. It's just less code due to being written in pure HTML and CSS I reckon. Correct me if I am wrong!

Thread Thread
darkwiiplayer profile image
DarkWiiPlayer

Yea, I don't think the way I do it is all that different from yours, the only detail that really changes is that I use more of the CSS cascade in an attempt to make it more "generic" :D

Collapse
link2twenty profile image
Andrew Bone

This is pretty cool and a great way to get people up and running fast there's one minor change I'd make though.

import { useEffect, useState } from "react";

export default function ScrollToTop() {
  const [isVisible, setIsVisible] = useState(false);

  // Top: 0 takes us all the way back to the top of the page
  // Behavior: smooth keeps it smooth!
  const scrollToTop = () => {
    window.scrollTo({
      top: 0,
      behavior: "smooth"
    });
  };

  useEffect(() => {
    // Button is displayed after scrolling for 500 pixels
    const toggleVisibility = () => {
      if (window.pageYOffset > 500) {
        setIsVisible(true);
      } else {
        setIsVisible(false);
      }
    };

    window.addEventListener("scroll", toggleVisibility);

    return () => window.removeEventListener("scroll", toggleVisibility);
  }, []);

  return (
    <div className="scroll-to-top">
      {isVisible && (
        <div onClick={scrollToTop}>
          <h3>Go up!</h3>
        </div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

By moving the function into the useEffect it is only declared once rather than on every draw. Also by adding a return to that same useEffect we can stop listening if the component is ever unmounted.

As an interesting side point you could look into using IntersectionObserver, with a scroll event fallback, if you wanted to improve on performance, but I don't think this would really need it 😉

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

Thank you for pointing out the cleanup for useEffect. I always tend to forget that. I had not considered IntersectionObserver since I didn't know there was a performance hit, but I will certainly look into it!

Collapse
bramus profile image
Bramus!

You don't need JS for this, as you achieve this with only CSS.

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

☝️ If you now link to #top, your browser will smoothscroll to the top of the page (you don't even need an anchor/element with that name)

DONE. 😱

(Not supported in Safari though — Meh 😕)

Collapse
duongductrong profile image
Dương Đức Trọng • Edited
window.scrollTo({
      top: 0,
      behavior: "smooth"
    });
Enter fullscreen mode Exit fullscreen mode

not working on safari browser. can u fix it?

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

Since I use Chrome and Firefox, I didn't know it doesn't work on Safari.

This post on Stack Overflow has some suggestions you might find helpful.
stackoverflow.com/questions/560112...

Collapse
duongductrong profile image
Dương Đức Trọng

helpful ! thank bro

Collapse
sarveshtheabstractor profile image
Sarvesh Hiwase

Very nice post but it would be great if you provided the hosted link so others can see the final result.
You could use GitHub pages, netlify, vercel, etc

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

Thank you for the feedback. I tried to get a basic Code Sandbox up and running but Chrome kept on crashing on me.

And the example I used is from my portfolio that I am currently working on. I will post a link as soon as it's live!

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

codesandbox.io/s/sharp-archimedes-...

Here you go! A very ugly looking demo for time being! :)

Collapse
anthonybrown profile image
Tony Brown

Not very smooth

Thread Thread
prnvbirajdar profile image
Pranav Birajdar Author

The 'smooth' functionality differs from browser to browser I guess. It works great for Chrome and Firefox. Not so sure about Safari though!

Collapse
pompolutz profile image
Oleh Lutsenko

And of course you want to remove event listener as well? :)

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

Just updated the code. Thank you for the comment!

Collapse
ricardobeat profile image
Ricardo Tomasi • Edited

Just like back navigation, this functionality is in fact so important that every computer already has a physical button for it: the "Home" key, or shift+space / cmd+up if you don't have one.

Duplicating system or browser behaviour is simply introducing UI inconsistency - not a fan of these.

Collapse
prnvbirajdar profile image
Pranav Birajdar Author • Edited

Damn, two decades of using computers and I didn't know a 'Home' key already does that for you! 🤯

Collapse
ms_yogii profile image
Yogini Bende

Simple yet very useful tutorial. Thanks for sharing 🙌

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

Glad you found it useful!

Collapse
emmgfx profile image
Josep Viciana

Nice, but I think hooks should start with use, like useScrollToTop.

Collapse
prnvbirajdar profile image
Pranav Birajdar Author

You're right. I think this can be called a Scroll component then! 😅