How to Detect When a Sticky Element Gets Pinned

By  on  

The need for position: sticky was around for years before it was implemented natively, and I can boast that I implemented it with JavaScript and scroll events for ages. Eventually we got position: sticky, and it works well from a visual perspective, but I wondered how can we determine when the element actually became pinned due to scroll.

We can determine if an element has become sticky thanks to the IntersectionObserver API!

Pinning an element to the top of its container is as easy as one CSS directive:

.myElement {
  position: sticky;
}

The question still remains about how we can detect an element being pinned. Ideally there would be a :stuck CSS directive we could use, but instead the best we can do is applying a CSS class when the element becomes sticky using a CSS trick and some JavaScript magic:

.myElement {
  position: sticky;
  /* use "top" to provide threshold for hitting top of parent */
  top: -1px;
}

.is-pinned {
  color: red;
}
const el = document.querySelector(".myElement")
const observer = new IntersectionObserver( 
  ([e]) => e.target.classList.toggle("is-pinned", e.intersectionRatio < 1),
  { threshold: [1] }
);

observer.observe(el);

As soon as myElement becomes stuck or unstuck, the is-pinned class is applied or removed. Check out this demo:

See the Pen WNwVXKx by David Walsh (@darkwing) on CodePen.

While there's not too much JavaScript involved, I hope we eventually get a CSS pseudo-class for this. Something like :sticky, :stuck, or :pinned seems as reasonable as :hover and :focus do.

Recent Features

  • By
    Send Text Messages with PHP

    Kids these days, I tell ya.  All they care about is the technology.  The video games.  The bottled water.  Oh, and the texting, always the texting.  Back in my day, all we had was...OK, I had all of these things too.  But I still don't get...

  • By
    An Interview with Eric Meyer

    Your early CSS books were instrumental in pushing my love for front end technologies. What was it about CSS that you fell in love with and drove you to write about it? At first blush, it was the simplicity of it as compared to the table-and-spacer...

Incredible Demos

  • By
    Create Twitter-Style Dropdowns Using jQuery

    Twitter does some great stuff with JavaScript. What I really appreciate about what they do is that there aren't any epic JS functionalities -- they're all simple touches. One of those simple touches is the "Login" dropdown on their homepage. I've taken...

  • By
    dat.gui:  Exceptional JavaScript Interface Controller

    We all love trusted JavaScript frameworks like MooTools, jQuery, and Dojo, but there's a big push toward using focused micro-frameworks for smaller purposes. Of course, there are positives and negatives to using them.  Positives include smaller JS footprint (especially good for mobile) and less cruft, negatives...

Discussion

  1. Rob Simpson

    Is there anyway to have a fallback for IE11?

  2. Isabella

    So the reason this works is because the observer is tracking if the item is 100% on screen. By setting -1px on the top, the item becomes only 99.99% on screen, thus triggering the observer.

    Am I understanding that right?

  3. Jonas

    I would go for :stuck since it feels best semantically.

  4. This is great!! As I found that the class was being assigned at points of the intersection that weren’t needed in my case, another slightly different solution: https://codepen.io/svsdesign/pen/YzGLrbj

    (I hope you don’t mind me sharing )

  5. Stephan Mönninghoff

    This also assigns the .is-pinned class if the element touches the bottom of the screen, which is likely if the section this is applied to is relatively high and you are browsing on a mobile device.

  6. Sean Rasmussen

    This was nice. Went on a little journey, afterwards, learning all about InteresectionObservers. Very useful!

  7. I had been lost trying to find a modern way to implement “Scrollspy” (a JS tool in the Bootstrap world), and your approach was exactly what I was looking for! Thank you! This was so helpful!!

  8. Fernando

    We can also use the option rootMargin: '-1px' of the intersection observer instead of the top: -1px on the css.

  9. Unfortunately I don’t think we’ll ever see a :stuck pseudo class. It can lead to a styling infinite loop.

    header {
      position: sticky;
      top: 0;
    }
    
    header:stuck {
      position: static;
    }
    

    And because of this spec writers are keeping clear of it.

    • Mark

      You could say the same for :hover

  10. This solution doesn’t fully work if you scroll past the sticky element(so if #parent also has a parent with more height to it…). That use case applies to element that shouldn’t be sticky until the bottom of the page, I guess we then need a second observer that checks if the bottom of the parent reached the bottom of the viewport and combine it to this first observer :p

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!