Smooth scroll & sticky navigation (#2/3)

Remy Sharp
Jul 2, 2017 · 5 min read

In the ffconf — the 2016 edition, the day before the site was to be launched, I decided that I wanted to make the navigation sticky.

Part 1 was the deconstructing the original jQuery method down to regular JavaScript. Then I wanted to add scrolling smoothing. Then I realised I’d opened a can of worms.

Components of the problem

The desired effect was that once I scroll past the navigation, it would stick, and I could click a link and it would smooth scroll to that location.

The video above shows that in action, but you can try it for yourself at There’s a few distinct problems to solve here:

  1. A sticky element once it hits the top of the viewport
  2. Headings that are linked to, need to adjust their position to below the elememnt
  3. Smooth scroll to each link
  4. The back button should work

I should also credit Jeremy Keith’s post as I only solved (2) with Jeremy’s post.

Sticky element

The sticky element was navigation, and had it been sitting at the top of the page it would be simpler, as it would always have position: fixed applied to it. As it was though, my navigation element only becomes sticky at a certain threshold.

I did originally consider if I could use the IntersectionObserver (used in an inverse way), but it didn't fit at all.

The prerequisites to getting a solid and non-janky sticky element are:

  1. Track the position with onscroll -> requestAnimationFrame -> calc, specifically, defer the work until rAF fires
  2. Only switch to position: fixed when the element is just about to hit the viewport boundary
  3. Only toggle the sticky state when the element goes in and out of the boundary, i.e. don’t keep applying the state

Here’s the documented code to track and apply the sticky class to fix the position of the navigation element. Note that I'm applying the sticky class to the body element, I'll explain in a moment.

Here is the CSS that accompanies the sticky header:

Probably the most important bit is the body.sticky adding 100px to the padding-top. This is because the height of the navigation is also 100px and when it changes from position: static to fixed, it's removed from layout. So to adjust for the loss of the navigation element, I'm pushing the whole of the content down by 100px, which creates a seamless scroll, instead of a jump (as seen in the video below).

Linking to headings

Now that the navigation is sticky, if we click on one of the links inside the navigation the page will jump, but the navigation element is sitting on top of the heading. We don’t want this.

Example of the navigation visually over the heading
Example of the navigation visually over the heading

To fix this, the targeted element is offset by the height of the navigation element (100px in my case):

The CSS above creates a solid block directly before the targeted element, all h2 for my conference site, and pushes them down by enough so that it sets them below the sticky navigation.

One quirk, is that this can be seen when you scroll up manually. Though it’s small enough in the scheme of the design that it doesn’t warrant addressing.

Smooth scrolling

This is where things get hairy. There’s a very good smooth scroll vanilla JavaScript library that I found out about…a month too late. Admittedly though, this combination of requirements means that smooth-scroll would also fall foul.

I decided to write my own, partly because I expected it to be straight forward, and partly because I’m naïve like that. That said, I wanted to use a simply tweening function, but I couldn’t work it out, and opted for using Soledad Penadés’ tween library (so not entirely vanilla…more neapolitan).

The code follows below with comments to document:

This does the trick (and if you’re copying my code, you’ll need the tween.js library included in your scripts), but I also needed to add the if (running) since I'm starting the rAF call on every click, otherwise the rAF call keeps running and in this instance, it racks up every time I click.

In the final part, I’ll throw away all my JavaScript and see how to redo this all with just CSS.

Originally published on Remy Sharp’s b:log


Remy Sharp’s b:log (syndicated)

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store