0

I have a page which makes use of the following:

  • Headroom.js for a header which "slides out of view when scrolling down and slides back in when scrolling up"
  • HTML <a> links to internal sections
  • Smooth scrolling

I want to ensure that, when the page scrolls to a particular anchor (be that due to clicking a link on the page to e.g. #bottom, or manually adding the hash fragment to the URL, or following a redirect), the header doesn't get in the way of the anchor.

You can reproduce the problematic behaviour I'm talking about in the snippet below. Click the anchor to jump to the bottom of the page, and then click the link to jump back to the top.

Note that the header covers up the content which the browser has scrolled to.

How can I, in a robust manner, ensure that any scrolls due to navigating to an anchor element are offset by the height of the header (if visible) such that content appears in the right place?

One of the approaches I've experimented with involves unpinning the header as soon as a hashchange is detected, a la:

$(window).on('hashchange',function(){
    setTimeout(() => {
        headroom.unpin();
    }, 0);
});

But for a smooth scroll, the hashchange event fires before the scrolling has finished. hence the header just gets repinned immediately. I also don't see any way to detect when the smooth scroll has finished.

Adding a click listener on the <a> elements is not robust because it fails for users arriving at the page with a hash fragment already in the URL. It also fails for JavaScript changes to window.location.hash. Similarly, listening on the hashchange event is also unreliable, because it doesn't fire if the user clicks an anchor, scrolls away and then clicks the same anchor again.

document.addEventListener("DOMContentLoaded", function(e) {
  var headroom = new Headroom(document.querySelector(".header"), {
    "offset": 205,
    "tolerance": 5,
    "classes": {
      "initial": "animated",
      "pinned": "slideDown",
      "unpinned": "slideUp"
    }
  });
  headroom.init();
});
html {
  scroll-behavior: smooth;
}

.header {
  position: fixed;
  z-index: 10;
  right: 0;
  left: 0;
  top: 0;
  background-color: #292f36;
  color: white;
  text-align: center;
  padding: 2rem 0;
}

p:nth-of-type(1) {
  margin-top: 7rem;
}

body {
  font-family: sans-serif;
}

.header {
  animation-duration: 0.5s;
  animation-fill-mode: both;
  will-change: transform, opacity;
}

@keyframes slideDown {
  0% {
    transform: translateY(-100%)
  }
  100% {
    transform: translateY(0)
  }
}

.slideDown {
  animation-name: slideDown;
}

@keyframes slideUp {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-100%);
  }
}

.slideUp {
  animation-name: slideUp;
}
<script src="https://npmcdn.com/headroom.js/dist/headroom.min.js"></script>
<div class="header">Here is my header</div>
<p id="top">Here is some text. Click to <a href="#bottom">jump</a> to the bottom of the page.</p>
<ul>
  <li>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.</li>
  <li>Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.</li>
  <li>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi.</li>
  <li>Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices ut, elementum vulputate, nunc.</li>
</ul>

<ul>
  <li>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.</li>
  <li>Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.</li>
  <li>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi.</li>
  <li>Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices ut, elementum vulputate, nunc.</li>
</ul>
<ul>
  <li>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.</li>
  <li>Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.</li>
  <li>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi.</li>
  <li>Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices ut, elementum vulputate, nunc.</li>
</ul>
<ul>
  <li>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.</li>
  <li>Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.</li>
  <li>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi.</li>
  <li>Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices ut, elementum vulputate, nunc.</li>
</ul>
<ul>
  <li>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.</li>
  <li>Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.</li>
  <li>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi.</li>
  <li>Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices ut, elementum vulputate, nunc.</li>
</ul>
<ul>
  <li>Morbi in sem quis dui placerat ornare. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.</li>
  <li>Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.</li>
  <li>Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi.</li>
  <li>Pellentesque fermentum dolor. Aliquam quam lectus, facilisis auctor, ultrices ut, elementum vulputate, nunc.</li>
</ul>

<p id="bottom">Here is some more text. Click to <a href="#top">jump</a> to the top of the page.</p>
Adam Williams
  • 1,712
  • 3
  • 17
  • 30
  • 1
    `smooth` is a pretty vaguely defined thing, quote MDN: _“The scrolling box scrolls in a smooth fashion using a user-agent-defined timing function over a user-agent-defined period of time. User agents should follow platform conventions, if any.”_ - so I doubt you’ll have any luck trying to figure out when it ends, unless browsers will provide an API in the future maybe to read those values. Maybe implementing “smooth scroll” via JavaScript might make more sense here, that would give you a lot more control. – 04FS Jun 07 '19 at 13:48
  • Or you bind your own handler function for the scroll event, so that you can figure out if scrolling is still happening (google keywords: javascript scrollend event), and combine that with a click handler on your anchor links setting some sort of flag, so that you can differentiate between scrolling triggered by those, or by “normally” scrolling using mouse wheel/cursor keys etc. – 04FS Jun 07 '19 at 13:51
  • I fear you're right, wrt using JS instead here. One of the challenges with looking for the scroll event to stop firing is that the user can scroll as much as they want *whilst* the smooth scroll is happening which terminates the animation. At that point, how can you possibly differentiate between the browser initiated events, and the user initiated events? – Adam Williams Jun 07 '19 at 13:53
  • 1
    Yeah, but I mean how likely is that? How often do you click on an anchor link, and then do your own scrolling while automatic scrolling is already happening? Sounds like rather an edge case to me, than what I would expect the majority of users to actually do. And maybe even that could be caught, to some extent, if you watch mousewheel, cursor key and other events that might relate to the user actively triggering scrolling on their own as well. – 04FS Jun 07 '19 at 13:57
  • "How often do you click on an anchor link, and then do your own scrolling while automatic scrolling is already happening?" - true, you'd only do this if you wanted to break things :P I'm only being picky because it feels like a 'hack', but I guess it's the best we can realistically do right now. – Adam Williams Jun 07 '19 at 14:12
  • @04FS If you want to write a "You're better off implementing the smooth scroll with JS" answer for this question, I'll accept it. – Adam Williams Jun 09 '19 at 13:12

0 Answers0