0

I'm trying out Intersection Observer API but I have come to a dead end when I use setInverval with it.

So I have this div which have width and height of 500px. If the div is in viewport I want to trigger setInterval which will log something every 3 seconds. But if I scroll and the div is outside the viewport I want to clear that interval. That is, stop the interval and stop the logging altogether.

Right now when I scroll and the div is in viewport, the setInterval and the logging starts but if I scroll again and the div is not in the viewport the setInterval and the logging doesn't stop.

What am I doing wrong here?

Here's the snippet:

const mainContainer = document.querySelector('#main-container');

const callback = (entries, observer) => {

  entries.forEach(item => {

    let intervalVar;

    if (item.isIntersecting) {

      intervalVar = setInterval(() => {

        console.log('div is in viewport');

      }, 3000);

    } else {

      console.log('div is not in viewport');

      clearInterval(intervalVar);

    }

  })
};


const options = {
  threshold: 0.1
};

const observer = new IntersectionObserver(callback, options);
observer.observe(mainContainer);
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

<div id="main-container" style="width: 540px; height: 440px;"></div>
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Zak
  • 860
  • 16
  • 39
  • 1
    The callback is called every time the intersection changes, so `intervalVar` is redeclared (it's also redeclared for each entry). Either create a Map that holds interval ids for every entry that needs one, or, better yet, don't use intervals in the observer callback at all. Just trigger a function when `item.isIntersecting` is true and fire it when it's false. That function can internally keep track of ids and decide what to do with those ids based on whether it's called with true or false. – Heretic Monkey Aug 04 '21 at 16:56
  • Can you provide a snippet? – Zak Aug 04 '21 at 17:08

1 Answers1

1

So, here's one way of tackling the problem.

I've created a Map between an entry from the intersection observer callback's entries argument and the interval id (intersections).

I've created a function (intersectionChanged) that gets called for each entry in the intersection observer callback's entries argument. It checks to see if the entry is intersecting, and whether we have an interval id yet. If both are true, it clears the existing interval and adds a new one. If it's newly intersecting, it adds a new interval. If it's not intersecting, and there's an interval id, it clears that interval. Otherwise, it does nothing. In other words, if it's not intersecting, and no interval id exists, it doesn't do anything.

Putting them together was easy with the code you already had built.

const mainContainer = document.querySelector('#main-container');
const intersections = new Map();
const intersectionChanged = function (entry) {
  if (entry.isIntersecting && intersections.get(entry.target) != null) {
    clearInterval(intersections.get(entry.target));
    intersections.set(entry.target, setInterval(() => {
        console.log('div is in viewport');
      }, 3000));
  } else if (entry.isIntersecting) {
    intersections.set(entry.target, setInterval(() => {
        console.log('div is in viewport');
      }, 3000));
  } else if (!entry.isIntersecting && intersections.get(entry.target) != null) {
    console.log('div is not in viewport');
    clearInterval(intersections.get(entry.target));
  }
};
const callback = (entries, observer) => {
  entries.forEach(intersectionChanged);
};

const options = {
  threshold: 0.1
};

const observer = new IntersectionObserver(callback, options);
observer.observe(mainContainer);
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

<div id="main-container" style="width: 540px; height: 440px;border:1px solid black"></div>
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
  • 1
    Wow. I have always avoided Map object cause I thought it's really complicated but it's super simple. I understand the full code now. Pretty slick. Thanks mate. – Zak Aug 04 '21 at 17:55