For the tl;dr people, I’ve included the full code at the end. For the rest, I’ll walk you through the source.
The HTML would look like this. Note how the
href attribute shares the id of the heading element.
Then we loop through the link elements to attach the click event listeners. I used an old-fashioned
scroll() function doesn’t exist yet. That’s our next task!
There’s nothing fancy here – most of the cool stuff happens in the
animateScroll() function that we’ll soon get into. Before calling the function however, there are three things we must do.
- Prevent the default action of the click – that’s navigating straight to the target
- Get a reference to the target element. We strip the “#” from the beginning of the
hrefand call the
getElementById()function of the
documentobject with the result
- Get the vertical displacement of the target element from the top. We pass this value to the
We’re now ready to move to the
This one’s takes a bit more effort. At first glance we see that there’s a plenty of variable definitions and an inner helper function. I’ve split the function into smaller pieces below to deal with them one by one. Let’s start with the variables.
On the first line we redefine the
targetHeight parameter. The inline conditional looks a bit convoluted but what we do is basically pretty simple. We check if the target height is so close to the bottom of the page that it’s impossible to scroll so low that the target is at the top of the page. If that’s the case, we set the bottom of the page as the target.
After this we grab the current scroll position and set this to a variable. This is needed in the animation loop, as the user is not necessarily at the top of the page when clicking the link.
step_x variable will help us later when we get serious with the cosine function.
step-count is used for – counting steps. We’ll be using the frame count as a cue for stopping the animation. The all-capital
SCROLL_DURATION tells us the duration of the animation in frames. The all-caps signals that it’s a fixed constant value.
step() function performs a single step of the animation. The line with the
window.scrollTo() call is the actual page scrolling. In addition to that, we increment the
step_counter, check if there are still frames left to animate and if so, well request another one, calling the function itself recursively.
Let’s look at the math used in the y-coordinate of the
scrollTo() call. First of all we have the
initialPosition value that we saved earlier. Then we have a rather complicated looking calculation. It might be easier to understand in the form of an equation (you can also skip the math if it feels like too much)
What we’re trying to accomplish here is to progress the scrolling along a cosine squared curve. This makes for a nice smooth transition in the style of Bezier curve. The curve starts at
initialPosition and ends at
targetHeight, as it should.
There’s one final thing that hasn’t been dealt with yet and that just happens to be the actual animation. As you can see, there are no
setInterval() calls in the code. Instead we are using the
requestAnimationFrame() function2 that’s so useful in web animation that it justifies the horribly long name.
The function is used in two places in the code. Right after defining our variables in
animateScroll() we call it with the
step() function as callback to initialize the animation. After this every animation loop recursively calls for another frame to be rendered.
1. Yeah, this one is just for demonstration. Click me to get back.
2. The animation method also is the reason for the one inconsistency in the code. How long does it take for the scroll animation complete? As a matter of fact, that depends on your monitor. The `requestAnimationFrame()` function calls a new frame based on the refresh rate of the hardware. That means that depending on your hardware, the animation duration can be for example half a second (30 frames / 60 Hz = 0.5 seconds) or barely a quarter (30 frames / 120Hz = 0.25 seconds). There are ways to find out the framarate by for example measuring it from two consecutive calls to `requestAnimationFrame()`. That, however shall be left for future development. I may one day post an update with the functionality added but in the mean time I recommend it as an exercise for the reader.