Browser APIs Available for Scrolling
Browsers largely support a few different methods of scrolling to different parts of the page using native APIs.
The ones we’ll cover include:
- scrollIntoView: scrolls an HTML element into view
- scroll / scrollTo: scrolls to a set of x y coordinates
- scrollBy: scrolls a given amount of pixels
All of these can cover different use cases depending on what you’re trying to achieve.
Part 1: Scrolling to an Element with scrollIntoView
Starting from the top is scrolling to an element.
Here we’ll use the scrollIntoView method that’s made available on an Element instance which gives us the simplest way of scrolling to an element.
In order to use scrollIntoView, we need to have access to the Element instance. which we can do by either using a Ref or by gaining access another way such as dynamically querying for the element.
Scrolling to an element with Refs
Using Refs, we can first create and set a ref on our element:
import { useRef } from 'react';
const MyComponent () => {
const myRef = useRef<HTMLElement | null>(null);
return (
<div>
<h1>My Page</h1>
<p>
<button>Jump to My Section</button>
</p>
{/* Content */}
<section ref={myRef} id="my-section">
My Section
</section>
</div>
)
}
Once the app is ready in the browser, any action we take afterwards should give us access to myRef and the Element instance of My Section via our Ref.
If we update our button element, we can then use that ref and scrollIntoView to jump to our element:
<button onClick={() => myRef.current?.scrollIntoView() }>
Jump to My Section
</button>
When the button is clicked, the page should jump to My Section.
Even better, we can add smooth scrolling by passing in a behavior option:
<button onClick={() => myRef.current?.scrollIntoView({
behavior: 'smooth'
}) }>
Jump to My Section
</button>
Now anytime someone clicks our button, it does so by animating the scrolling of the page down to that element!
Scrolling by querying an element
If we don’t have access to the Ref or we want to dynamically look our Element up, we can instead query our selector and scroll to it.
Using the same example where My Section has an ID of my-section
, we can use getElementById or querySelector to find the element, then scroll to it.
<button onClick={() => {
const element = document.getElementById('my-section');
element?.scrollIntoView({
behavior: 'smooth'
});
}}>
Jump to My Section
</button>
This can be particularly handy if you’re propagating events to a parent element or if the data is loading dynamically making it challenging to add a ref to every instance.
Part 2: Scrolling to the Top of the Page with scroll & scrollTo
Scrolling to the top of the page isn’t necessarily an element, but it’s a location on the page that’s typically marked by an element.
Historically, one way of providing a “Jump to Top” link was to use an anchor link and a fragment, which works well, but requires a little extra markup.
That works according to spec:
If fragment is the empty string, then return the special value top of the document.
Scrolling to the top of the page with scroll
If we want to provide a Jump to Top button, we can provide our coordinates via:
<button onClick={() => window.scroll(0, 0)}>
Jump to Top
</button>
This assumes we want a 0 used for the X coordinate, otherwise we can dynamically update that.
That said, we could also use the options object and specify only a top
property instead:
<button onClick={() => {
window.scroll({
top: 0
});
}}>
Jump to Top
</button>
Where at that point, similar to Part 1, we can provide a smooth scroll experience by passing a behavior:
<button onClick={() => {
window.scroll({
top: 0,
behavior: 'smooth'
});
}}>
Jump to Top
</button>
Scrolling with scrollTo
Interestingly, the scrollTo method works pretty much exactly the same as scroll.
According to w3 spec, scrollTo effectively works as if scroll was invoked.
When the scrollTo() method is invoked, the user agent must act as if the scroll() method was invoked with the same arguments.
That means, we can technically just update our code from the previous example to scrollTo.
<button onClick={() => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}}>
Jump to Top
</button>
Though honestly, it probably makes sense to simply use scroll as its the authoritative method.
Part 3: Scrolling to Coordinates with scroll
When scrolling to different parts of a page, elements or the very top of the page make convenient locations to scroll to, but our scrollIntoView method can only reference the top or bottom of an element and we don’t always have a simple 0 to use for our coordinate.
A simple example could be, perhaps whenever we scroll to an element, we don’t want to simply scroll to the top, we want to provide some padding above.
Or another example could be a more complicate, responsive, dynamic page that doesn’t have obvious elements to query and jump to, but instead, needs to be calculated depending on different conditions.
Whichever the scenario, we can use our same scroll and scrollTo methods to achieve this, only we now specify the elements coordinates.
Scrolling to an element with padding before element
Given our example of adding some padding above our jump point, let’s figure out what that would look like.
First, we need to find our element and once we have it, use its position relative to the page and subtract (or add) the amount we want to offset the location when jumped.
<button onClick={() => {
const element = document.getElementById('my-section');
if ( !element ) return;
window.scroll({
top: element.offsetTop - 20,
behavior: 'smooth'
})
}}>
Jump to My Section
</button>
Removing 20 gives us a nice little bit of padding before the element so the header isn’t completely hugging the top of the page.
Note: offsetTop might not always be a bulletproof solution as it may change what the location is relative to depending on how you’re positioning the element and its parent.
Part 4: Scrolling Page by Page with scrollBy
Another tool in our belt that we can use for scrolling a fixed amount is the scrollBy method, which allows us to pass in a pixel value for the page to scroll from its current location.
This contrasts scroll as those values are relative to the top and left side of the page, where scrollBy is relative to the current scroll point.
Scrolling a “page” worth of content
One potential use case is to provide an experience that allows the window to be scroll as if it were going to a new “page” of content, such as if you’re reading a long blog post, the reader is at the end of the visible text, and wants to go another “page”.
Note: the page by page scrolling is somewhat similar to if you press the spacebar key when focused on a page of content.
To do this, we’ll need to again dynamically determine the value we want to scroll, which in our case, will be the viewport size.
<button onClick={() => window.scrollBy(0, window.innerHeight)}>
Next Page
</button>
Here we pass in a 0 for X and the window’s innerHeight which is our viewport height.
If we wanted to add smooth scroll we can pass it in as an options object:
<button onClick={() => {
window.scrollBy({
top: window.innerHeight,
behavior: 'smooth'
})
}}>
Next Page
</button>
Controlling scroll for better UX
These were simpler examples, but we have different options for how we can control scrolling depending on the environment and our needs.
A word of caution though is to try to avoid performing unnatural scrolling behavior or hijacking the visitors scrolling experience, which has been coined “scrolljacking“.
Your best bet will to use these methods to perform scrolling actions based on something your visitor triggers, providing an intuitive behavior that enhances the experience instead of taking away from it.