Scroll-To-Top Functionality With React
6 min read
Your webpage has lots of content. Maybe it has a long article. Maybe it has a long gallery of high-quality images. Surely, you want your users to skim or read all the way through the page. But sometimes, users want to immediately go back to the top of the page. They don't want to scroll manually up to the top of the page. As developers, we must provide the users of our web apps the best possible experience. For the aforementioned scenario above, a better UX is to give the user a way to quickly go to the top of the page.
Scroll-to-top
In this blog site of mine, it is inevitable that I'll have contents which go beyond the maximum height of any screen. For this reason, I have added a scroll-to-top
button at the bottom right of the screen. When that button is clicked, the page will automatically be scrolled up to the top of the page in a smooth manner.
I'd love to share with you how I achieved that functionality.
The Button
The button is just a simple one. It has position: fix
so that it is completely removed from the natural document flow. Inside it is a chevron svg
icon that points upward (similar to what you see in this page). Note that I'm using Tailwind CSS to style my button but you can use any valid way for styling yours.
const ScrollToTopButton = () => {
return (
<button
aria-label='scroll to top'
className='w-12 h-12 transition-transform duration-200 flex fixed right-10 bottom-10 bg-primary rounded-full shadow-lg shadow-gray-900 justify-center items-center'
>
<svg
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
strokeWidth={2}
stroke='currentColor'
className='w-6 h-6'
>
<path strokeLinecap='round' strokeLinejoin='round' d='M4.5 15.75l7.5-7.5 7.5 7.5' />
</svg>
</button>
);
};
export default ScrollToTopButton;
Next, let's add a scroll
event listener to the window
object. This is a good use-case for useEffect
since we want the listener to be added as soon as the component is mounted.
import { useEffect } from 'react';
const ScrollToTopButton = () => {
useEffect(() => {
const scrollCallback = () => {
const scrolledFromTop = window.scrollY;
};
window.addEventListener('scroll', scrollCallback);
scrollCallback();
// clean-up function
return () => {
window.removeEventListener('scroll', scrollCallback);
};
}, []);
// render here ...
};
export default ScrollToTopButton;
Let me explain what's happening here. First, we defined a callback function called scrollCallback
. Its job is to simply get the scrollY
poroperty of the window
object. The scrollY
property is the number of pixels that the document is currently scrolled vertically. In line 9, we are assigning that callback to be the function to be called once the page is scrolled. In line 11, we invoke the scrollCallback
function so that we can know immediately the how much is scrolled from top on first mount. Lastly, our useEffect returns a clean-up function to avoid unintended memory leaks.
💡 Info
To learn more about window.scrollY go to this link from MDN.
But the implementation does not stop there. What we want is to only show the button if a certain scroll threshold has been reached. For this we need a state.
import { useEffect, useState } from 'react';
const ScrollToTopButton = () => {
const [shown, setShown] = useState(false);
useEffect(() => {
const scrollCallback = () => {
const scrolledFromTop = window.scrollY;
setShown(() => scrolledFromTop > 300);
};
window.addEventListener('scroll', scrollCallback);
scrollCallback();
// clean-up function
return () => {
window.removeEventListener('scroll', scrollCallback);
};
}, []);
return (
<button
aria-label='scroll to top'
className={`${
shown ? 'scale-100' : 'scale-0'
} w-12 h-12 transition-transform duration-200 flex fixed right-10 bottom-10 bg-primary rounded-full shadow-lg shadow-gray-900 justify-center items-center`}
>
{/* svg icon here*/}
</button>
);
};
export default ScrollToTopButton;
For this example, my threshold is 300. When scrollY
is greater than that, shown
state becomes true and false otherwise. Then, the way I hide the button is by simply assigning CSS transform (scale) to it depending on the shown
state.
Now, let's finally add onClick
handler for the button so that when it's clicked, the page will be scrolled to top. And that is a simple as this:
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
<button
aria-label='scroll to top'
onClick={scrollToTop}
className={`${
shown ? 'scale-100' : 'scale-0'
} w-12 h-12 transition-transform duration-200 flex fixed right-10 bottom-10 bg-primary rounded-full shadow-lg shadow-gray-900 justify-center items-center`}
>
{/* svg icon here*/}
</button>
);
That's it! Simple right? But let's not just stop there. We can still do better by extracting a useScrollToTop
hook. We do this as follows:
import { useEffect, useState, useCallback } from 'react';
export default function useScrollToTop(threshold: number = 300) {
const [shown, setShown] = useState(false);
useEffect(() => {
const scrollCallback = () => {
const scrolledFromTop = window.scrollY;
setShown(() => scrolledFromTop > threshold);
};
window.addEventListener('scroll', scrollCallback);
scrollCallback();
return () => {
window.removeEventListener('scroll', scrollCallback);
};
}, []);
const scrollToTop = useCallback(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, []);
return { shown, scrollToTop };
}
The only difference is that now we're accepting a threshold
parameter so that the user of our hook is free to provide any positive number as a threshold. Also, note that scrollToTop
is now memoized using useCallback
and we're returning it with shown
as an object.
We use the hook like this:
import useScrollToTop from './useScrollToTop.ts';
const ScrollToTopButton = () => {
const { shown, scrollToTop } = useScrollToTop(300);
return (
<button
aria-label='scroll to top'
onClick={scrollToTop}
className={`${
shown ? 'scale-100' : 'scale-0'
} w-12 h-12 transition-transform duration-200 flex fixed right-10 bottom-10 bg-primary rounded-full shadow-lg shadow-gray-900 justify-center items-center`}
>
{/* svg icon here*/}
</button>
);
};
export default ScrollToTopButton;
And that's it!
Conclusion
This is exactly how the scroll-to-top button in this page is implemented. I hope you learned something from this post. Until next time, happy coding!
-jep