I’m working a project right now that has some auto-scroll/page-jumping behavior. Not a super advanced thing to do – take an element with an ID, append that ID to the URL, and boom, it’s automatically focused by the browser.
Go ahead, just type #second_p
at the end of the URL in your address bar and this paragraph should be right at the top of the page.
We can trigger this behavior with JavaScript using the scrollTo
function of the window
object. Let’s markup our page, first.
<body>
<header>
<!--a bunch of navigation>
</header>
<main>
<div class='hero'>
<!--probably an image and CTA, height of at least 100vh-->
</div>
<section id='main_content'>
<!--finally, page content!-->
</header>
</main>
<footer>
<!--copywrong notice-->
</footer>
</body>
The section
with the ID set to main_content
is what we’re trying to target here. To do that, we need to start by defining where we want the window to scroll and figuring out how we want the scrolling effect to behave. The window.scrollTo
property will take an element’s x
and y
(vertical and horizontal) offsets (as top
and left
), then scroll the window to that offset. The function will also accept behavior
, which can be smooth
, instant
, or auto
. Passing smooth
animates the scroll, while instant
removes the animation and is a jump. auto
lets the browser choose, but in my experience isn’t 100% reliable and usually chooses instance
.
Implementation
To start, we’ll select the element and find out where it is on the page using element.getBoundingClientRect()
, which will return a bunch of values relating to the dimensions and location relative to both the page and window. We’ll pass the top
and left
properties to the scrollTo
functions, and pass 'smooth'
for the behavior.
document.addEventListener('DOMContentLoaded', () => {
var el = document.querySelector('#main_content');
var rect = el.getBoundingClientRect();
window.scrollTo({
top: rect.top,
left: rect.left,
behavior: "smooth"
});
})
Putting it Together
I put that all together in the demo below – with a 3 second delay on the scrolling action.
See the Pen Window Scroll Demo by Nate Northway (@the_Northway) on CodePen.
But even with that, there are some issues. When we animate something with CSS, we’re always able to check if the user wants to see animations using media queries, specifically checking for prefers-reduced-motion
. Luckily, we can do that with JS too, and control the behavior
value that is passed to window.scrollTo()
. To do that, we need to start by defining a query and listening for settings to change without the page being reloaded. We can do that with window.matchMedia()
, which accepts the media query we want to watch for as the argument. In return, we’ll get a MediaQueryList
, or false
if the media query doesn’t match. The MediaQueryList
won’t update when the conditions change, so we will have to update the variable we store it to when it does. Fortunately, we can add an event listener to the variable to listen for changes! Let’s add that in to our code. There is a built in function that is part of the MediaQueryList
, but that’s being deprecated. So I have opted to use the good ole’ addEventListener
function.
document.addEventListener('DOMContentLoaded', () => {
var mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
var reduceMotion = mediaQuery.matches;
mediaQuery.addEventListener("change", () => {
reduceMotion = mediaQuery.matches;
})
var el = document.querySelector('#main_content');
var rect = el.getBoundingClientRect();
window.scrollTo({
top: rect.top,
left: rect.left,
behavior: mediaQuery.matches?"instant":"smooth"
});
})
All of that is in the demo below, again with a 3 second delay.
See the Pen Window Scroll with Media Query in JS by Nate Northway (@the_Northway) on CodePen.
We can use this for a whole lot of media queries that change behavior based on screen size, accessibility settings, and more. We should use this to listen for changes to screen size and accessibility settings, especially when animations are part of the page and especially when those animations aren’t controlled by CSS exclusively.