1

IntersectionObserver seems to be triggering well before it should in my scenario (yes, I've read quite a few Q&A's here and several guides published around the place).

I'm applying a class after an observed element leaves the viewport. This works in general as you scroll down the page, but on page load, the next element, outside the viewport (could be #box2 or #box3 depending on your browser height) gets the viewed class, before it ever enters the viewport. Here's a codepen.

Can anyone explain why the observed element after the one/s that are in viewport have the viewed class (.js-was-active) on page load, before they intersect the viewport – and the (presumably obvious) error I've made somewhere?!

Bonus, possibly related, question (while trying to debug the issue above, I shifted the bottom margin of the observer up 50%, but it's triggering as soon as any observed elements enter the viewport, not halfway up, despite rootMargin: '0% 0% -50% 0%'. Why?

const steps = document.querySelectorAll('.js-iobserve');
const ioConfig = { rootMargin: '0% 0% -50% 0%' };
var isLeaving = false;
observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    var match = '.' + entry.target.getAttribute('id');
    if (entry.isIntersecting) {
      isLeaving = true;
      entry.target.classList.add('js-is-active');
      $(match).addClass('js-is-active');
    } else if (isLeaving) {
      isLeaving = false;
      entry.target.classList.remove('js-is-active');
      entry.target.classList.add('js-was-active');
      $(match).removeClass('js-is-active').addClass('js-was-active');
    }
  });
}, ioConfig);

$(document).ready(function() {
  steps.forEach(step => {
    observer.observe(step);
  });
});
.js-is-active {
  border: 4px dashed red;
}
.js-was-active {
  opacity: .5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header class="container-fluid py-4">
  <h1 class="text-center">HEADER</h1>
</header>

<div class="container mt-5">
  <div class="row">
    <div class="col">
      <div id="box1" class="d-flex justify-content-center align-items-center bg-info mb-5 js-iobserve" style="height:67vh;">BOX ONE</div>
      <div class="my-5 py-5"></div>
      <div id="box2" class="d-flex justify-content-center align-items-center bg-danger mb-5 js-iobserve" style="height:67vh;">BOX TWO</div>
      <div class="my-5 py-5"></div>
      <div id="box3" class="d-flex justify-content-center align-items-center bg-warning mb-5 js-iobserve" style="height:67vh;">BOX THREE</div>
      <div class="my-5 py-5"></div>
      <div id="box4" class="d-flex justify-content-center align-items-center bg-success mb-5 js-iobserve" style="height:67vh;">BOX FOUR</div>
      <div class="my-5 py-5"></div>
      <div id="box5" class="d-flex justify-content-center align-items-center bg-info mb-5 js-iobserve" style="height:67vh;">BOX FIVE</div>
      <div class="my-5 py-5"></div>
    </div>
    <div class="col">
      <div class="position-sticky" style="top:2rem;">
        <h2 class="box1">Box one heading</h2>
        <h2 class="box2">Box two heading <br/><small>Why does this have the active class on page load when my rootMargin is -50% on the bottom edge?</small></h2>
        <h2 class="box3">Box three heading <br/><small>Why does the io think I've seen this already on page load?</small></h2>
        <h2 class="box4">Box four heading</h2>
        <h2 class="box5">Box five heading</h2>
      </div>
    </div>
  </div>
</div>
Erland
  • 123
  • 10
  • What is your actual question, it looks like you are asking two at once? – cloned Aug 29 '22 at 06:34
  • @cloned have edited to clarify the main question (and explain the related rootMargin bit) – Erland Aug 29 '22 at 21:34
  • Don't @me about mixing and matching with jQuery I can't remove it from my production site so I may as well use it for brevity. – Erland Aug 29 '22 at 21:35

1 Answers1

0

The elements after the first element in the viewport have the js-is-active class because of the logic in your callback.

You're setting isLeaving to false. Whenever you initialize the page the Intersection Observer loops over every element that is observed that is either in or out of view.

That means that the element in your view sets isLeaving to true. The loop then continues over the next elements that are observed and because isLeaving is now true the classes will be set.

A solution is to evaluate the classes on the observed target and handle your logic according to classes they do or do not have.

I also took the liberty to connect your step elements to the box elements with a Map. That way you don't have to look up the box every time the callback is fired.

const steps = document.querySelectorAll('.js-iobserve');
const ioConfig = { 
  root: null,
  rootMargin: '0px 0px -500px 0px' ,
  threshold: 0
};

const boxMap = new Map();
for (const step of steps) {
  const id = step.dataset.box;
  const box = document.querySelector(`#${id}`);
  if (box !== null) {
    boxMap.set(step, box);
  }
}

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    const match = boxMap.get(entry.target);
 
    if (entry.isIntersecting) {
      if (!entry.target.classList.contains('js-was-active')) {
        entry.target.classList.add('js-is-active');
      }
    } else {
      if (entry.target.classList.contains('js-is-active')) {
        entry.target.classList.remove('js-is-active');
        entry.target.classList.add('js-was-active');
        match.classList.remove('js-is-active');
        match.classList.add('js-was-active');
        observer.unobserve(entry.target);
      }
    }
  });
}, ioConfig);

steps.forEach(step => {
  observer.observe(step);
});
.js-is-active {
  border: 4px dashed red;
}
.js-was-active {
  opacity: .5;
}
<header class="container-fluid py-4">
  <h1 class="text-center">HEADER</h1>
</header>

<div class="container mt-5">
  <div class="row">
    <div class="col">
      <div data-box="box1" class="d-flex justify-content-center align-items-center bg-info mb-5 js-iobserve" style="height:67vh;">BOX ONE</div>
      <div class="my-5 py-5"></div>
      <div data-box="box2" class="d-flex justify-content-center align-items-center bg-danger mb-5 js-iobserve" style="height:67vh;">BOX TWO</div>
      <div class="my-5 py-5"></div>
      <div data-box="box3" class="d-flex justify-content-center align-items-center bg-warning mb-5 js-iobserve" style="height:67vh;">BOX THREE</div>
      <div class="my-5 py-5"></div>
      <div data-box="box4" class="d-flex justify-content-center align-items-center bg-success mb-5 js-iobserve" style="height:67vh;">BOX FOUR</div>
      <div class="my-5 py-5"></div>
      <div data-box="box5" class="d-flex justify-content-center align-items-center bg-info mb-5 js-iobserve" style="height:67vh;">BOX FIVE</div>
      <div class="my-5 py-5"></div>
    </div>
    
    <div class="col">
      <div class="position-sticky" style="top:2rem;">
        <h2 id="box1">Box one heading</h2>
        <h2 id="box2">Box two heading <br/><small>Why does this have the active class on page load when my rootMargin is -50% on the bottom edge?</small></h2>
        <h2 id="box3">Box three heading <br/><small>Why does the io think I've seen this already on page load?</small></h2>
        <h2 id="box4">Box four heading</h2>
        <h2 id="box5">Box five heading</h2>
      </div>
    </div>
  </div>
</div>
Emiel Zuurbier
  • 19,095
  • 3
  • 17
  • 32