← Overview

stickyObserver

Marks a sticky element while it is pinned so CSS can style the stuck state.

Scroll this page. Each bar below travels with its section until it reaches the top of the viewport — there position: sticky pins it, the script flips is-stuck, and the bar gains an elevated surface with a hairline and a shadow — a state pure CSS cannot select, because there is no :stuck pseudo-class. Three independent instances run on this page: auto-injected sentinel, author-placed sentinel, and a bar inside a scroll container.

Bar one — auto-injected sentinel, pinned while it shows its surface

Keep scrolling — this text exists so the section is long enough for the bar above to actually pin. That bar carries nothing but data-js-behavior="stickyObserver": the tiny sentinel element the observer watches was injected by the script right before the bar. Open the inspector and you will find it — an empty, zero-size <div> with aria-hidden="true" that exists only while JavaScript runs.

The pinning itself never depended on JavaScript. The bar is a plain position: sticky element, so with scripting disabled it would still attach to the viewport top at exactly the same scroll position — it would just keep its transparent in-flow look, and no sentinel would exist at all. What the script adds is purely the missing piece of information: a class that says "this element is pinned right now", which the stylesheet turns into the elevated surface.

There is no scroll listener running and nothing is measured per frame: an IntersectionObserver reports only the moments the sentinel crosses the viewport edge. Between two flips this page costs nothing. Scroll on — this bar unpins where its section ends, and the second instance takes over.

Bar two — author-placed sentinel, surface appears when the trigger paragraph is gone

This paragraph is the sentinel of bar two — marked with an id and referenced via data-sticky-sentinel="#surfaceTrigger". Watch closely: the bar pins the moment it reaches the viewport top, but it stays transparent while these lines slide underneath it. Only once this whole paragraph has scrolled past does the surface fade in.

That is the point of the second mode: the trigger is decoupled from the element. An author-placed sentinel can sit anywhere in the layout — a hero section whose passing should condense the site header, a marker deep inside an article — and any element works, not just an empty helper. It is also the escape hatch for markup where an injected sibling would not be free: inside a flex or grid container with a gap the extra child occupies a slot, and list or table markup does not allow a stray <div> between its items.

Both instances on this page run independently — each owns its sentinel and its observer, and several elements could even share one sentinel by pointing at the same selector. Scroll back to the top now and watch the hand-over in reverse: as each trigger re-enters the viewport its bar loses the class, the surface fades out, and the bar is an ordinary part of the page flow again.

The third bar does not care about the page scroll at all. It lives inside the framed overflow: auto box below and pins against its top edge — data-sticky-root="#demoScrollBox" hands that box to the observer as its root, because an element root is the only way the class can flip against the container edge the bar actually sticks to. Scroll inside the box to see it.

Bar three — pins inside this scroll container

Scroll this box. The bar above is an ordinary position: sticky element whose containing scroller happens to be this box, not the page — so it pins the moment the box's content starts moving underneath it, while the page itself stands still.

The wiring is the same as everywhere else: the script injected a zero-size sentinel right before the bar, and the observer reports the moments that sentinel crosses the box's top edge. The only difference is the observer's root — the box instead of the viewport.

Scroll back up inside the box and the bar releases: the sentinel re-enters the box, the class is removed, and the surface fades out again — completely independent of the two instances above.