D-Money
D-Money

Reputation: 2465

How to prevent intersection observer from firing when passing over elements quickly

In the code example below, there is a button to scroll to the bottom of the page, passing over six observed divs.

isIntersecting fires for each element as they pass through the viewport. Is it possible to throttle or add a delay to the trigger to prevent the intersection event for elements that are only briefly in the viewport?

Ideally, in the example below, the interaction counter would be 2 after clicking the scroll down button - one detection for the first element and one for the last element, ignoring those in between

const blocks = document.querySelectorAll('.block');
const counter = document.querySelector('.counter');
const observer = new IntersectionObserver(callback, {
  threshold: 0.5
});

let count = 0;

blocks.forEach(block => observer.observe(block));

function scrollDown(y = document.body.clientHeight) {
  window.scrollTo({
    top: y,
    behavior: 'smooth'
  });
}

function callback(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      count++;
      counter.innerText = `Intersections: ${count}`;
      entry.target.style.backgroundColor = 'lightblue';
    } else {
      entry.target.style.backgroundColor = 'lightpink';
    }
  });
};
.block {
  width: 50vw;
  height: 80vh;
  margin: 20px auto;
}

.counter {
  position: fixed;
  top: 50px;
  left: 20px;
}
<p class="counter"></p>
<button onclick="scrollDown();">Scroll To Bottom</button>

<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>

<button onclick="scrollDown(0);">Scroll To Top</button>

Upvotes: 3

Views: 5367

Answers (1)

Spectric
Spectric

Reputation: 31992

Here's one approach:

  • everytime an intersecting element is found, increment count and perform the necessary handling in the setTimeout callback set to run after 500 milliseconds (this number can be changed in accordance to the scroll speed)

  • store the number returned by setTimeout in a variable

  • when setting the setTimeout, check whether the variable contains a value. If so, use clearTimeout to remove that setTimeout, so whatever code you scheduled to execute won't run.

  • once you've stopped scrolling, the code in the callback of the setTimeout will execute, and only the last element scrolled to will be handled (since previous setTimeouts to handle the other elements were cleared).


const blocks = document.querySelectorAll('.block');
const counter = document.querySelector('.counter');
const observer = new IntersectionObserver(callback);

let count = 0;

blocks.forEach(block => observer.observe(block));

function scrollDown(y = document.body.clientHeight) {
  window.scrollTo({
    top: y,
    behavior: 'smooth'
  });
}

var lastTimeout;

function callback(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      if (lastTimeout) clearTimeout(lastTimeout)
      lastTimeout = setTimeout(function() {
        count++;
        counter.innerText = `Intersections: ${count}`;
        entry.target.style.backgroundColor = 'lightblue';
      }, 500);
    } else {
      entry.target.style.backgroundColor = 'lightpink';
    }
  });
};
.block {
  width: 50vw;
  height: 80vh;
  margin: 20px auto;
}

.counter {
  position: fixed;
  top: 50px;
  left: 20px;
}
<p class="counter"></p>
<button onclick="scrollDown();">Scroll To Bottom</button>

<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>

<button onclick="scrollDown(0);">Scroll To Top</button>

Upvotes: 2

Related Questions