<div class="page">
  <div class="sentinal"></div>
  <h1 class="header">Intersection observer <br/>&amp; position: sticky
  </h1>

  <ul class="Items">

    <li class="Item">
      <div class="Item_Img"></div>
      <div class="Item_Inner">
        <h2>Interaction Observer is watching the pink bar at the top of the screen.</h2>
      </div>
    </li>
    <li class="Item">
      <div class="Item_Img"></div>
      <div class="Item_Inner">
        <h2>As soon as it enters or leaves the viewport, an event is triggered.</h2>
      </div>
    </li>
    <li class="Item">
      <div class="Item_Img"></div>
      <div class="Item_Inner">
        <h2>The event contains an 'isIntersecting' value - which tells us if the pink bar is/isn't in the viewport.</h2>
      </div>
    </li>
    <li class="Item">
      <div class="Item_Img"></div>
      <div class="Item_Inner">
        <h2>We can use this event to do whatever we need, in this case, just toggling a class on the h1.</h2>
      </div>
    </li>
    <li class="Item">
      <div class="Item_Img"></div>
      <div class="Item_Inner">
        <h2>Unrelated to Intersection Observer, but also handy, 'position: sticky' is used on the h1.</h2>
      </div>
    </li>
    <li class="Item">
      <div class="Item_Img"></div>
      <div class="Item_Inner">
        <h2>This can all be accomplished without any scroll event listeners.</h2>
      </div>
    </li>

  </ul>

</div>
.sentinal {
  // DOM element for triggering the intersection observer event
  // you can use 'height' to determine when the event is triggered
  height: 3rem;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  background-color: #F10C49;
}

body {
  font-family: BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica", "Open Sans", "Arial", sans-serif;
  background-color: #f3eee8;
}

.page {
  padding: 6rem 0;
  max-width: 600px;
  margin: 0 auto;
}

.header {
  background-color: white;
  position: -webkit-sticky;
  position: sticky;
  top: 2rem;
  padding: 1rem;
  line-height: 1.5;
  box-decoration-break: clone;
  z-index: 1;
  transition: all 300ms;
  will-change: color, background-color;
  
  &.enabled {
    color: white;
    background-color: #A70267;
  }
}

h1 {
  display: inline;
  color: #F10C49;
  box-decoration-break: clone;
}

* {
  padding: 0;
  margin: 0;
}

.Items {
  color: #FB6B41;
  padding-top: 2rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  grid-gap: 2rem 3rem;
}

.Item {
  display: flex;
  flex-direction: column;
}

.Item_Img {
  position: relative;
  background-color: #EAE4E0;
  
  &::before {
    content: '';
    position: relative;
    padding-bottom: 56.25%;
    display: block;
  }
}

.Item_Inner {
  padding: 4rem 1.5rem 1.5rem;
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: white;
  justify-content: flex-end;
  min-height: 8rem;
}
View Compiled
const headerEl = document.querySelector('.header')
const sentinalEl = document.querySelector('.sentinal')

const handler = (entries) => {
  console.log(entries)
  // entries is an array of observed dom nodes
  // we're only interested in the first one at [0]
  // because that's our .sentinal node.
  // Here observe whether or not that node is in the viewport
  if (!entries[0].isIntersecting) {
    headerEl.classList.add('enabled')
  } else {
    headerEl.classList.remove('enabled')
  }
}

// create the observer
const observer = new window.IntersectionObserver(handler)
// give the observer some dom nodes to keep an eye on
observer.observe(sentinalEl)

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.