</>Jonathan Harrell

Main Menu

Site Tools

Controlling Element Visibility with the Intersection Observer API

Controlling the display of elements based on their visibility within the viewport has typically been a rather messy affair, involving calculations using window height and getBoundingClientRect(). There is now a new API that makes this much simpler called Intersection Observer. It is now supported in Chrome, Firefox, Opera and Edge and there is a good polyfill for it.

I decided to experiment and push IntersectionObserver to its limits:

Go to experiment

Controlling Element Visibility Using IntersectionObserver

Click here to view the experiment on Codepen

Note

You probably shouldn’t create as many observers on a production site as I have done. You start to run into performance issues. However, this experiment should help you visualize how the API works.

The HTML and CSS

Link to this section

I have a simple grid of cards that is styled using CSS grid:

<section class="card-grid">
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  ...
</section>
.card-grid {
  display: grid;
  grid-template-columns: repeat(
      auto-fill,
      minmax(100px, 1fr)
  );
  grid-gap: 45px;
}

Creating the Intersection Observers

Link to this section

I loop over each card and create an observer. The observer accepts a callback and an options object. Note that in options I am setting the rootMargin to a negative value. This insets the intersection point in from the viewport on all sides by 100px. So a card can be up to 100px in the viewport before the observer will read it as intersected.

The root margin grows or shrinks each side of the root element’s bounding box before computing intersections. 100 100 root margin

I have also set the threshold option as an array with two numeric values. These are essentially the percentages of intersection at which the observer will respond. So, when a card is 50% in the viewport and when it is 100% in, the observer will fire the callback.

const options = {
  rootMargin: '-100px',
  threshold: [0.5, 1]
}
const observer = new IntersectionObserver(callback, options);

const targets = document.querySelectorAll('.card');
targets.forEach(target => observer.observe(target));

Setup the Callback

Link to this section

The callback function gives me an array of entries – each entry is essentially an intersection change. I can check the intersectionRatio on each entry and apply some styling appropriately.

Thresholds indicate at what percentage of the target’s visibility the observer’s callback should be executed. 0% 0% 0% 50% 50% 50% 100% 100% 100%
const callback = entries => {
  entries.forEach(entry => {
    const ratio = entry.intersectionRatio;

    // look at the ratio and do stuff to each element
  });
}

I use a switch statement to apply different styling for different ratios:

switch (true) {
  case (ratio === 1):
    entry.target.style.opacity = 1;
    entry.target.style.transform = 'scale(1.25)';
    return;

  case (ratio < 1 && ratio >= 0.5):
    entry.target.style.opacity = 0.5;
    entry.target.style.transform = 'scale(1.1)';
    return;

  case (ratio < 0.5):
    entry.target.style.opacity = 0.15;
    entry.target.style.transform = 'scale(1.0)';
    return;

  default:
    return;
}

The Intersection Observer API provides a more straightforward and powerful method for checking element visibility relative to the viewport. Hopefully browser support continues to improve and we’ll be able to use it soon in production sites without needing a polyfill.

More Articles

Go to article

System-Based Theming with Styled Components

Learn how to support system-based theming in Styled Components, while allowing a user to select their preferred theme and persist that choice.

Go to article

Implicit State Sharing in React & Vue

Learn to use React’s Context API and provide/inject in Vue to share state between related components without resorting to a global data store.

Go to article

Component Reusability in React & Vue

Learn how to use render props in React and scoped slots in Vue to create components that are flexible and reusable.

Go to article

What’s the Deal with Margin Collapse?

Learn about margin collapse, a fundamental concept of CSS layout. See visual examples of when margin collapse happens, and when it doesn’t.