Fathy AR A dev blog: journal, notes, and more...

CSS and JS trick for sticky or fixed header overlapping targeted element

Posted on in Web Dev · 386 words · 2 minute read
Tagged with: CSS, JavaScript, sticky elements

Assume you have an HTML page with sticky/fixed non-scrolling header on top.

Then how should I solve the issue of the header overlapping a targeted element with an anchor (#element-id) i.e. make the browser scroll to the element but still respect the height of the fixed header as not to overlap the element?

Here is what I found to be the simplest solution from CSS Tricks1 which requires no JavaScript.

html {
  scroll-padding-top: 180px; /* height of sticky header */
}

But, then, in my case (in the development of this blog), the header does not have a fixed height i.e. the height changes relative to the content of navigation inside it.

The only way to work around this problem would be to keep detecting and tracking the changes of the header’s height via JavaScript.

The idea would be:

  1. Detect the initial height of the sticky/fixed header.
  2. Set value of the CSS property scroll-padding-top of <html>to the detected initial height.
  3. If there is a change in the height then update the value of scroll-padding-top.

To make it easier, why not just jump straight in to the code.

First, we will utilize CSS custom variable to store the value of fallback (if JS is disabled) and tracked (via JS) height of the sticky/fixed header.

html {
  --header-h-fb: 180px; /* the fallback value if JS is disabled */
  --header-h: ;       /* what we will get and manipulate via JS */
  --calculated-h: var(--header-h, var(--header-h-fb)); /* height of sticky header */
  scroll-padding-top: var(--calculated-h);
}

Now, we will go through the JS script.

"use strict";

document.addEventListener("DOMContentLoaded", function(event) {
    const siteHeader = document.querySelector("header");
    const syncHeight = (height) => {
        document.querySelector("html").style.setProperty("--header-h", height+"px");
        console.log("Setting value of <html>'s \"--header-h\" CSS property to "+height+"px");
    };

    const resizeObserver = new ResizeObserver((entries) => {
        // modified from https://usefulangle.com/post/319/javascript-detect-element-resize
        // Notice that this uses the entries[0].borderBoxSize[0].blockSize; instead of entries[0].contentRect.height; as mentioned in the tutorial
        // because we need its height as it is rendered by inspector.
        // Run this `console.log(entries[0])` to see the details.
        console.log(entries[0]);
        let height = entries[0].borderBoxSize[0].blockSize;
        console.log('Current height: ' + height+"px");
        syncHeight(height);
    });
    
    resizeObserver.observe(siteHeader); // Start observing for resize.
});


  1. Fixed Headers, On-Page Links, and Overlapping Content, Oh My! (https://css-tricks.com/fixed-headers-on-page-links-and-overlapping-content-oh-my/↩︎