Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intersection Observer: Call a function only once per element

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?

like image 542
stekhn Avatar asked Dec 08 '18 16:12

stekhn


1 Answers

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.

like image 103
stekhn Avatar answered Oct 28 '22 17:10

stekhn