IntersectionObserver: Responsive/Dynamic "threshold" option

33 views Asked by At

I am animating elements of a website once they come into viewport, and I want to have different threshold options for mobile and desktop.

An element that will animate contains class names, for example animation-observer anim-m-40 anim-d-100 anim-bounce-in. Once the element comes into viewport, the class animated is added and the defined animation (in this case anim-bounce-in) runs.

My threshold logic

Threshold can be defined in a dropdown by the steps 0.05, 0.10, 0.15, ... 1.

The above example explained:

  • in a mobile viewport < 600 pixels, the threshold of 0.4 should be used (defined by the class anim-m-40).
  • in a desktop viewport of >= 600 pixels the threshold of 1 should be used (defined by the class anim-d-100).

When resizing the viewport, the elements are unobservered, and then observed again, depending on the viewport width.

I have a working solution, but...

...it's way too many lines of code! It kind of hurts my eyes, to be honest.

I poured all my Javascript knowledge on how to simplify code using function etc into it, but just couldn't find a better solution, as the IntersectionObserver API is new to me.

The working code

I simplified the code a little by removing some lines and replacing them with // ....

The logic is still in tact:

const observerCallback = (elements, observer) => {
    // isIntersecting is true when element and viewport are overlapping
    // isIntersecting is false when element and viewport don't overlap
    elements.forEach(element => {
        if(element.isIntersecting === true) {
            let target = element.target;
            target.classList.add('animated');
            // we're done here. stop observing element
            observer.unobserve(target);
        }
    });
}

// default (fallback) observer
var observer_default = new IntersectionObserver(observerCallback, { threshold: [0.40] });

function initIntersectionObserver () {
    // mobile: create observeres in 0.05 steps
    window.observer_05 = new IntersectionObserver(observerCallback, { threshold: [0.05] });
    window.observer_10 = new IntersectionObserver(observerCallback, { threshold: [0.10] });
    // ...
    window.observer_95 = new IntersectionObserver(observerCallback, { threshold: [0.95] });
    window.observer_100 = new IntersectionObserver(observerCallback, { threshold: [1] });
}


// creating a function to handle viewport change
function initObserverElements(viewport_has_changed = false){
    // default (fallback)
    let elements_to_observe = document.querySelectorAll('.animation-observer:not([class*="anim-m"]):not([class*="anim-d"]):not([class*="animated"])');
    elements_to_observe.forEach(element => { observer_default.observe(element); });
    // conditionally unobserve elements
    if (viewport_has_changed === true) {
        elements_to_unobserve = document.querySelectorAll('.animation-observer[class*="anim-m"][class*="anim-d"]');
        elements_to_unobserve.forEach(element => { window.observer_05.unobserve(element); });
        elements_to_unobserve.forEach(element => { window.observer_10.unobserve(element); });
        // ...
        elements_to_unobserve.forEach(element => { window.observer_95.unobserve(element); });
        elements_to_unobserve.forEach(element => { window.observer_100.unobserve(element); });
    }
    // mobile first
    if( window.innerWidth < 600) {
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-05:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_05.observe(element); });
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-10:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_10.observe(element); });
        // ...
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-95:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_95.observe(element); });
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-m-100:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_100.observe(element); });
    }else {
        // desktop
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-05:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_05.observe(element); });
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-10:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_10.observe(element); });
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-15:not([class*="animated"])');
        // ...
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-95:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_95.observe(element); });
        elements_to_observe = document.querySelectorAll('.animation-observer.anim-d-100:not([class*="animated"])');
        elements_to_observe.forEach(element => { observer_100.observe(element); });
    }
}

// Adding eventlistner for window resize
var resizeTimeout;
function resizeThrottler() {
    // ignore resize events as long as an actualResizeHandler execution is in the queue
    if ( !resizeTimeout ) {
        resizeTimeout = setTimeout(function() {
            resizeTimeout = null;
            var viewport_has_changed = true;
            initObserverElements(viewport_has_changed);
            // The actualResizeHandler will execute at a rate of 15fps
        }, 66);
    }
}

window.addEventListener("resize", resizeThrottler, false);

// init on page load
initIntersectionObserver();
initObserverElements();

Appreciate any hint!

0

There are 0 answers