29

I am trying to make it so that as some text items stop overlapping a dark background, they will individually change color one by one as the user scrolls. All of the text items are position: fixed

EDIT: The MDN docs say (emphasis mine):

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element

I think this means there is no way to solve my problem because the elements I want to monitor for overlap are not children of the root I am specifying in the options object.

Is there any way to detect overlap if the overlapping element is not a child of the other element?

if ("IntersectionObserver" in window) {
  const options = {
    root: document.getElementById("flow-landing"),
    rootMargin: "0px",
    threshold: 0,
  };

  var callback = function (entries, observer) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.style.color = "white";
      } else {
        entry.target.style.color = null;
      }
    });
  };

  const observer = new IntersectionObserver(callback, options);

  var targets = [
    Array.from(document.querySelectorAll(".social-item")),
    Array.from(document.querySelectorAll(".additional-item")),
  ].flat();

  targets.forEach((target) => observer.observe(target));
}

There aren't any console errors but the code isn't doing anything.

diedu
  • 19,277
  • 4
  • 32
  • 49
Ollie Williams
  • 1,996
  • 3
  • 25
  • 41
  • 1
    The complete quote from MDN is as follows: *"The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element **or with a top-level document's viewport."*** Replace `document.getElementById('flow-landing')` with `null`. Read the whole article at least twice because it's hard to visualize the explanation at times. – zer00ne Apr 26 '19 at 09:42

3 Answers3

32

Modifying Ruslan's answer a little because in his answer, multiple Intersection Observer objects are being created.

It is possible to observe multiple elements using the same observer by calling .observe() on multiple elements.

let observerOptions = {
    rootMargin: '0px',
    threshold: 0.5
}

var observer = new IntersectionObserver(observerCallback, observerOptions);

function observerCallback(entries, observer) {
    entries.forEach(entry => {
        if(entry.isIntersecting) {
          //do something
        }
    });
};

let target = '.targetSelector';
document.querySelectorAll(target).forEach((i) => {
    if (i) {
        observer.observe(i);
    }
});
Ziyad
  • 402
  • 5
  • 8
  • 4
    Are there any drawbacks to this method in terms of performance ? ( using observer on multiple elements ) – xperator Aug 25 '21 at 10:18
  • Probably better performances to listen on (even many) specific elements, than to manually poll on the (all) the elements from the page. I think the browsers did test the api for the best performance (possible). Potential issues logically will depend on the client computational power or on number of element observed. [MDN has an article talking about past performance issues](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). – Kamafeather Jul 05 '22 at 01:54
  • What is `if (i)` used for in: `document.querySelectorAll(target).forEach((i) => { if (i) { ...` ? – Pedro P. Camellon Jul 27 '22 at 21:11
  • 1
    @PedroP.Camellon : `i` is an _item_ index in the `forEach` loop callback. My understanding is the `if (i) {...}` has nothing to do with the asked question, but is rather a specific need for OP in his app logic, (first element of the items returned by `querySelectorAll` is ignored because i equals 0, therefore `if (i)` resolves to false, meaning the first element is not observed for intersection. I believe you know that and therefore just ignore that part of the explanation, and just imagine it's only required for OP's case. We have no need to write that if part in our code. – Karl Stephen Jul 29 '22 at 08:19
  • 2
    @KarlStephen arguments of `forEach` as positional. `i` in this case in an item, not and index – Konrad Aug 29 '22 at 11:06
  • @KonradLinkowski Oh, good catch; indeed, I can't believe I messed that (facepalm). – Karl Stephen Aug 29 '22 at 11:31
  • @KarlStephen, in your defense, forEach() is a very strange function that returns first an element and second an index ... moreover you can't use break; on it... mmm! To forbid ? | -> Array.forEach((item, i) => {}); – SNS - Web et Informatique Mar 20 '23 at 03:48
8

you can do something like that, at least it helps me:

document.querySelectorAll('.social-item').forEach((i) => {
    if (i) {
        const observer = new IntersectionObserver((entries) => {
            observerCallback(entries, observer, i)
        },
        {threshold: 1});    
        observer.observe(i);
    }
})

const observerCallback = (entries, observer, header) => {
    entries.forEach((entry, i) => {
         if (entry.isIntersecting) {
             entry.target.style.color = "white";
         }
         else {
             entry.target.style.color = null;
         }
    });
};
Bodzio
  • 2,440
  • 2
  • 19
  • 37
Ruslan Korkin
  • 3,973
  • 1
  • 27
  • 23
  • Hi thanks for this - its exactly what ive been looking for. but Im struggling to get it working. Can you tell me what 'entries' is in your example? is it the objects that scroll under the fixed social items? – ToddPadwick May 26 '21 at 08:58
  • 1
    @ToddPadwick here are the target '.social-item' DOM items – Ruslan Korkin May 27 '21 at 09:57
  • `entries` are an API representing the act of _Observing_ a specific part of the DOM. Property `.target` is the element observed for entering/intersecting the window viewport. See: [IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry). – Kamafeather Jul 05 '22 at 02:03
  • Be cautious when using this approach because if multiple IntersectionObservers are in the viewport, they may trigger simultaneously (if you only want to track a single element). – Jose A Apr 29 '23 at 12:11
-8

You could use the offsetTop and offsetHeight properties instead of the IntersectionObserver API.

For example, when the user scrolls, you can check to see if the offsetTop of element 2 is greater than the offsetTop and less than the offsetHeight of element 1.

WARNING: use debounce because the scroll event's handler function will be called once for every pixel the user scrolls, just imagine the performance nightmare that would occur if user scrolled 600-1000 pixels.

The LoDash documentation, describes it's debounce function as:

"[a function that] creates a debounced function that delays invoking func (handler) until after wait (time) milliseconds have elapsed since the last time the debounced function was invoked."

If you aren't using LoDash, here is a Vanilla JavaScript debounce function:

function debounce(handler, time) {
  var timeout;
  return function() {
    var self = this, args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      return handler.apply(self, args);
    }, time);
  };
}

Here is the code that'll allow you to "do stuff" if element 2 "intersects" element 1.

let element_1 = document.querySelector("#my-element-1");
let element_2 = document.querySelector("#my-element-2");
window.addEventListener("scroll", debounce(() => {
  if(element_2.offsetTop > element_1.offsetTop && element_2.offsetTop < element_1.offsetHeight) {
    console.log("The elements are intersecting.");
  }
}, 100));

In case that looks complex or hard to read, here is the same code, broken up into smaller chunks:

let element_1 = document.querySelector("#my-element-1");

let element_2 = document.querySelector("#my-element-2");

window.addEventListener("scroll", debounce(() => {

  let x = element_2.offsetTop > element_1.offsetTop;

  let y = element_2.offsetTop < element_1.offsetHeight;

  if(x && y) {

    console.log("The elements are intersecting.");

  }

}, 250));

Notes & information:

  1. you could use the >= and <= operators instead of the > and < operators
  2. a wait time that's too long could make the effect look unnatural and forced.

Good luck.

Malekai
  • 4,765
  • 5
  • 25
  • 60
  • 6
    The Intersection Observer API replaces the method you mentioned since listening to Scroll Events is done synchronously so it's blocking and less performant – Yonatan Galili Mar 17 '21 at 14:04