Skip to content

IntersectionOberserver with newly appearing Elements

In a react project we have an element “stickyButton” that is fixed on the bottom of the viewport on mobile. It is supposed to be displayed as long as none of some other buttons are visible. So, we try to use IntersectionObserver to check if these buttons are visible

useEffect(() => {
    let stickyButton = document.getElementById("stickyButton");
    function handler(entries) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) {
                intersecting.push(entry);
            } else {
                intersecting.pop();
            }
        });

        if (intersecting.length > 0) {
            stickyButton.classList.add("hide");
        } else {
            stickyButton.classList.remove("hide");
        }
    }

    let options = {
        threshold: 0.05
    };

    let observer = new IntersectionObserver(handler, options);
    document.querySelectorAll('div.buttons').forEach(button => observer.observe(button));

    return () => observer.disconnect();
},[visibleHosts]);

Each time a button becomes visible we add it to an array “intersecting” and each time one becomes invisible again, we remove one from this array. As long as one button is visible, we hide the sticky button, when none are visible we show it.

Works fine so far, but alas, at the end of our list we have another button that loads eight more entities. This changes the variable “visibleHosts”, which gets our useEffect to reinitiate. So far that’s what we wanted.

But here is the problem: the new entity could very well be added by react in the viewport so it would count as visible. But since we never enter the handler function for it, it is never added to our “intersecting” array. So we suddenly have a difference between the number of elements in the array and the number of elements actually visible. And of course from there on out the sticky button no longer behaves as expected.

Is there a way to check all observed elements for visibility beyond doing it by hand (which would render the usage of IntersectionObserver pretty much moot?)

Answer

After a bit of trial and error we found the solution by not just pushing and popping the entries, but instead pushing entries.target if it is not already in the list and instead of popping filtering only entries.target from the list. That worked way better then our previous version.