skip to Main Content

I’m exploring the IntersectionObserver API and can’t figure out how the root:null option works. The documentation says that if null is passed, the observer will watch for intersection relative to the device’s viewport. But in my example, the callback function is executed when the element is crossing the boundaries of the parent element, not the viewport. Why is this happening?

Here is example

const second = document.querySelector('.second');

const options = {
  root: null, // to watch for intersection relative to the device's viewport
  threshold: 1,
};

const callback = (entries, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      console.log('second element is intersecting with VIEWPORT');
      document.querySelector('.second').style.background = '#2d1176';
    } else {
      console.log('second element is NOT intersecting with VIEWPORT');          
      document.querySelector('.second').style.background = 'red';
    }
    
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

const observer = new IntersectionObserver(callback, options);

observer.observe(second)
body {
  background: gray;
}
.wrapper {
  border: 2px solid red;
  margin: 40px;
  height: calc(100vh - 80px);
  overflow-y: scroll;
}

.first,
.second,
.third {
  height: 40vh;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: #2d1176;
  border-bottom: 15px solid black;
  color: #fff;
}

.third {
  height: 1000vh;
}
<div class="wrapper">
  <section class="first">
    <h1>⬇ First ⬇</h1>
  </section>
  <section class="second">
    <h1>⬇ Second (observable) ⬇</h1>
  </section>
  <section class="third">
    <h1>⬆︎ Third ⬆︎</h1>
  </section>
</div>

Just scroll the content to the second section and you will see "second element is intersecting with VIEWPORT" in console. Why does it happen? This intersection is not with the viewport, but with the parent element. But I passed null in root argument.

2

Answers


  1. When the root option is set to null, it means that the target element is being observed in relation to the viewport, not its parent element. So in your example, the callback function is executed when the element is crossing the boundaries of the viewport, not its parent element. If you want to observe the intersection of the element relative to its parent, you need to specify the parent element as the root

    Login or Signup to reply.
  2. It is behaving correctly.

    With threshold 1 that means the whole of the element must be observable within the viewport. As soon as it goes out of view (because the parent has overflow: hidden) even a tiny amount it is no longer totally observable.

    If you change the parent to overflow-y: visible you can see that as soon as the WHOLE of the element becomes visible within the viewport the observer is triggered.

    const second = document.querySelector('.second');
    
    const options = {
      root: null, // to watch for intersection relative to the device's viewport
      threshold: 1,
    };
    
    const callback = (entries, observer) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          console.log('second element is intersecting with VIEWPORT');
          document.querySelector('.second').style.background = '#2d1176';
        } else {
          console.log('second element is NOT intersecting with VIEWPORT');          
          document.querySelector('.second').style.background = 'red';
        }
        
        // Each entry describes an intersection change for one observed
        // target element:
        //   entry.boundingClientRect
        //   entry.intersectionRatio
        //   entry.intersectionRect
        //   entry.isIntersecting
        //   entry.rootBounds
        //   entry.target
        //   entry.time
      });
    };
    
    const observer = new IntersectionObserver(callback, options);
    
    observer.observe(second)
    body {
      background: gray;
    }
    .wrapper {
      border: 2px solid red;
      margin: 40px;
      height: calc(100vh - 80px);
      overflow-y: visible;
    }
    
    .first,
    .second,
    .third {
      height: 40vh;
      text-align: center;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background-color: #2d1176;
      border-bottom: 15px solid black;
      color: #fff;
    }
    
    .third {
      height: 1000vh;
    }
    <div class="wrapper">
      <section class="first">
        <h1>⬇ First ⬇</h1>
      </section>
      <section class="second">
        <h1>⬇ Second (observable) ⬇</h1>
      </section>
      <section class="third">
        <h1>⬆︎ Third ⬆︎</h1>
      </section>
    </div>

    Also worth experimenting with is setting the threshold to 0. That means even the tiniest bit of the element being visible within the viewport will trigger the observer.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search