Targeting position:sticky elements that are currently in a 'stuck' state
There is currently no selector that is being proposed for elements that are currently 'stuck'. The Postioned Layout module where position: sticky
is defined does not mention any such selector either.
Feature requests for CSS can be posted to the www-style mailing list. I believe a :stuck
pseudo-class makes more sense than a ::stuck
pseudo-element, since you're looking to target the elements themselves while they are in that state. In fact, a :stuck
pseudo-class was discussed some time ago; the main complication, it was found, is one that plagues just about any proposed selector that attempts to match based on a rendered or computed style: circular dependencies.
In the case of a :stuck
pseudo-class, the simplest case of circularity would occur with the following CSS:
:stuck { position: static; /* Or anything other than sticky/fixed */ }
:not(:stuck) { position: sticky; /* Or fixed */ }
And there could be many more edge cases that would be difficult to address.
While it's generally agreed upon that having selectors that match based on certain layout states would be nice, unfortunately major limitations exist that make these non-trivial to implement. I wouldn't hold my breath for a pure CSS solution to this problem anytime soon.
In some cases a simple IntersectionObserver
can do the trick, if the situation allows for sticking to a pixel or two outside its root container, rather than properly flush against. That way when it sits just beyond the edge, the observer fires and we're off and running.
const observer = new IntersectionObserver(
([e]) => e.target.toggleAttribute('stuck', e.intersectionRatio < 1),
{threshold: [1]}
);
observer.observe(document.querySelector('nav'));
Stick the element just out of its container with top: -2px
, and then target via the stuck
attribute...
nav {
background: magenta;
height: 80px;
position: sticky;
top: -2px;
}
nav[stuck] {
box-shadow: 0 0 16px black;
}
Example here: https://codepen.io/anon/pen/vqyQEK
Someone on the Google Developers blog claims to have found a performative JavaScript-based solution with an IntersectionObserver.
Relevant code bit here:
/**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--top` become visible/invisible at the top of the container.
* @param {!Element} container
*/
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
// Started sticking.
if (targetInfo.bottom < rootBoundsInfo.top) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [0], root: container});
// Add the top sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--top');
sentinels.forEach(el => observer.observe(el));
}
I haven't replicated it myself, but maybe it helps someone stumbling over this question.