5

I am trying to work with the Intersection Observer API. I have a function which works in my first iteration. The basic logic is that if the user scrolls down and adds or removes items from a basket, once the basket is in view again (as it is at the top of the document) then I fire an API call.

The issue is that it will not fire the function before scrolling, I want to trigger it if the item is visible or becomes visible again after scrolling (the second part is working)

Here is original js:

var observerTargets = document.querySelectorAll('[id^="mini-trolley"]');
var observerOptions = {
    root: null, // null means root is viewport
    rootMargin: '0px',
    threshold: 0.01 // trigger callback when 1% of the element is visible
}
var activeClass = 'active';
var trigger = $('button');
var isCartItemClicked = false;

trigger.on('click', function() {
    isCartItemClicked = true;
});

function observerCallback(entries, observer) { 
    entries.forEach(entry => {
        if(entry.isIntersecting && isCartItemClicked){
            $(observerTargets).removeClass(activeClass);
            $(entry.target).addClass(activeClass);
            isCartItemClicked = false;
            console.log('isCartItemClicked and in view');
            // do my api call function here
        } else {
            $(entry.target).removeClass(activeClass);
        }
    });
}

var observer = new IntersectionObserver(observerCallback, observerOptions);
[...observerTargets].forEach(target => observer.observe(target));

I have updated this so it now checks if the item is visible. so I have updated:

if(entry.isIntersecting && isCartItemClicked)

to

if((entry.isVisible || entry.isIntersecting) && isCartItemClicked)

The issue as I understand is that the observer is only triggered on scroll, but the entry.isVisible is part of the observer callback function.

I have made a JSFIDDLE here (which has HTML and CSS markup).

Is it possible to modify the code. Weirdly the MDN page does not mention the isVisible property, but it is clearly part of the function.

enter image description here

lharby
  • 3,057
  • 5
  • 24
  • 56
  • 1
    entry.isVisible is in proposal draft and is currently supported by only Chrome. https://web.dev/intersectionobserver-v2/ . I'm not sure about the working of `isVisible` but I believe it returns if the element to be observed is visible in viewport or not (opacity > 0, and not covered by any other element). – Kanishk Anand Jan 26 '22 at 15:23
  • ah OK, well that means I need another method, something like viewport offset top greater than cart item height. – lharby Jan 26 '22 at 15:45
  • Yah, you can use the BoundingClientRect maybe to check that? – Kanishk Anand Jan 26 '22 at 16:13

1 Answers1

1

This one is a little tricky but can be done by creating a someObserverEntriesVisible parameter that is set by the observerCallback. With that in place we can define how the button triggers should be handled separately from the observer callback for each intersecting entry.

const observerTargets = document.querySelectorAll('[id^="mini-trolley"]');
const observerOptions = {
    root: null, // null means root is viewport
    rootMargin: '0px',
    threshold: 0.01 // trigger callback when 1% of the element is visible
};
const activeClass = 'active';
const trigger = $('button');

let isCartItemClicked = false;
let someObserverEntriesVisible = null;
let observerEntries = [];

trigger.on('click', () => {
    isCartItemClicked = true;
    if (someObserverEntriesVisible) {
        console.log('fired from button');
        observerCallback(observerEntries, observer, false);
    }
});

function observerCallback(entries, observer, resetCartItemClicked = true) {
    observerEntries = entries;
    someObserverEntriesVisible = false;
    entries.forEach(entry => {
        someObserverEntriesVisible ||= entry.isIntersecting;
        if (entry.isIntersecting && isCartItemClicked) {
            $(entry.target).addClass(activeClass);
            // add API call here
            if (resetCartItemClicked) {
                isCartItemClicked = false;
                console.log('fired from observer');
            }
        } else {
            $(entry.target).removeClass(activeClass);
        }
    });
}

const observer = new IntersectionObserver(observerCallback, observerOptions);
[...observerTargets].forEach(target => observer.observe(target));
#content {
  height: 500px;
}

.active {
  background-color: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="mini-trolley">Observer target1</div>
<button>Top button</button>
<div id="content"></div>
<div id="mini-trolley">Observer target2</div>
<button>Bottom button</button>
GenericUser
  • 3,003
  • 1
  • 11
  • 17
  • 1
    This looks great, I am going to try and implement it soon, and feedback. Thank you. – lharby Jan 26 '22 at 16:16
  • 1
    @lharby Made one additional adjustment. I noticed that from button trigger all entries were being sent to `handleCartVisibleAndItemClicked`, rather than the visible ones. Added a function to filter to just the visible elements. – GenericUser Jan 26 '22 at 16:23
  • 1
    @lharby I just noticed a simplification here. If you store the entries from observerCallback, you can reference them directly from the button. This lets us clean up quite a bit of code so that it's now just the trigger click handler and the observerCallback. – GenericUser Jan 27 '22 at 13:09
  • OK cool, you have edited yes? I think this is not right :D `visible ||= entry.isIntersecting;` I guess should be `visible || entry.isIntersecting;` – lharby Jan 27 '22 at 13:20
  • 1
    Nope, that's correct. It's roughly equivalent to `visible = visible || entry.isIntersecting;` (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment). It means the same thing, just a little more concise. – GenericUser Jan 27 '22 at 13:21
  • 1
    Oh wow, thanks I have never used it. I can't give you any more points, but thank you for making the code more succinct. – lharby Jan 27 '22 at 13:23
  • Happy to help :) – GenericUser Jan 27 '22 at 13:23