17

I'm using the Intersection Observer API to track the visibility of multiple element on a web page. When an element becomes visible, a function callback() should be executed. The restriction: For each element, the function may only be executed once.

Here is my current implementation for a web analytics project:

const elements = document.querySelectorAll('[data-observable]');
const callback = str => { console.log(str); };
const observer = new IntersectionObserver(handleIntersection);

elements.forEach(obs => {
  observer.observe(obs);
});

function handleIntersection(entries, observer){
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      // Call this function only once per element, without modifying entry object
      callback('observer-' + entry.target.getAttribute('data-observable'));
    }
  });
}

I'm struggeling to find a solution that does not modify existing elements, the IntersectionObserver or the IntersectionObserverEntries.

Usually I would use a closure to ensure that a function gets only executed once:

function once(func) {
  let executed = false;
  return function() {
    if (!executed) {
      executed = true;
      return func.apply(this, arguments);
    }
  };
}

But in this case I have difficulties applying the function because IntersectionObserver uses a weird callback iterator logic that get's executed everytime any element changes (instead of using a event-driven model).

Any ideas, how to implement a once per element function call that does not mutate other elements or objects?

stekhn
  • 1,969
  • 2
  • 25
  • 38
  • Its not supported by IE, did you aware of that? – Madhu Dec 08 '18 at 16:56
  • Yes, but I'm using a polyfill. – stekhn Dec 08 '18 at 17:11
  • 5
    Do you still need the observer after the the functions called? If not, unobserve it once the function is called. https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/unobserve – James Dec 20 '18 at 03:37
  • Yes, because I'm observing multiple elements. Registering one observer per element on the other hand would be possible but super wasteful. – stekhn Dec 23 '18 at 14:33
  • 1
    but you can use function unobserve only for one element: observer.unobserve(entry.target); For other elements observer will still calling – Elizaveta Jan 15 '19 at 15:14

1 Answers1

44

As James pointed out in the comments, the easiest solution to this problem is unobserving the element once it has become visible and the callback has been invoked.

const elements = document.querySelectorAll('[data-observable]');
const callback = str => { console.log(str); };
const observer = new IntersectionObserver(handleIntersection);

elements.forEach(obs => {
  observer.observe(obs);
});

function handleIntersection(entries, observer){
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      callback('observer-' + entry.target.getAttribute('data-observable')); 
      observer.unobserve(entry.target);
    }
  });
}

I didn't find any viable solution to use a closure to control how often a function can be called.

stekhn
  • 1,969
  • 2
  • 25
  • 38