Event to detect when position:sticky is triggered
Demo with IntersectionObserver (use a trick):
// get the sticky elementconst stickyElm = document.querySelector('header')const observer = new IntersectionObserver( ([e]) => e.target.classList.toggle('isSticky', e.intersectionRatio < 1), {threshold: [1]});observer.observe(stickyElm)
body{ height: 200vh; font:20px Arial; }section{ background: lightblue; padding: 2em 1em;}header{ position: sticky; top: -1px; /* ➜ the trick */ padding: 1em; padding-top: calc(1em + 1px); /* ➜ compensate for the trick */ background: salmon; transition: .1s;}/* styles for when the header is in sticky mode */header.isSticky{ font-size: .8em; opacity: .5;}
<section>Space</section><header>Sticky Header</header>
The top
value needs to be -1px
or the element will never intersect with the top of the browser window (thus never triggering the intersection observer).
To counter this 1px
of hidden content, an additional 1px
of space should be added to either the border or the padding of the sticky element.
Demo with old-fashioned scroll
event listener:
- auto-detecting first scrollable parent
- Throttling the scroll event
- Functional composition for concerns-separation
- Event callback caching:
scrollCallback
(to be able to unbind if needed)
Here's a React component demo which uses the first technique
I found a solution somewhat similar to @vsync's answer, but it doesn't require the "hack" that you need to add to your stylesheets. You can simply change the boundaries of the IntersectionObserver to avoid needing to move the element itself outside of the viewport:
const observer = new IntersectionObserver(callback, { rootMargin: '-1px 0px 0px 0px', threshold: [1],});observer.observe(element);
If anyone gets here via Google one of their own engineers has a solution using IntersectionObserver, custom events, and sentinels:
https://developers.google.com/web/updates/2017/09/sticky-headers