Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force IntersectionObserver update

Tags:

Is there any way to force an update/run of an IntersectionObserver instance? The callback will be executed by default, when the viewport has changed. But I'm looking for a way to to execute it when other events happen, like a change of elements.

An Example:

On initialization everything works as expected. But when you change the position of the #red element, nothing happens.

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>

Is there any way to make this working? Only thing that would work is to unobserve the element and start observing it again. This may be work for an single element, but not if the Observer has hundreds of elements to watch.

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container')};
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
  observer.unobserve(red);
  observer.observe(red);
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>
like image 835
eisbehr Avatar asked Jul 18 '18 13:07

eisbehr


People also ask

What is threshold in IntersectionObserver?

threshold. Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5.

What is IntersectionObserver in Javascript?

The IntersectionObserver interface of the Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport. The ancestor element or viewport is referred to as the root.

What does intersection observer mean?

Chrome for Android? Android WebView? Samsung Internet? Opera Mobile? The IntersectionObserver interface can be used to observe changes in the intersection of an intersection root and one or more target Element s.


2 Answers

I don't think it is possible to force the intersection observer to update without calling unobserve/observe on the node, but you can do this for all observed nodes by saving them in a set:

class IntersectionObserverManager {
    constructor(observer) {
        this._observer = observer;
        this._observedNodes = new Set();
    }
    observe(node) {
        this._observedNodes.add(node);
        this._observer.observe(node);
    }
    unobserve(node) {
        this._observedNodes.remove(node);
        this._observer.unobserve(node);
    }
    disconnect() {
        this._observedNodes.clear();
        this._observer.disconnect();
    }
    refresh() {
        for (let node of this._observedNodes) {
            this._observer.unobserve(node);
            this._observer.observe(node);
        }
    }
}

Edit: use a Set instead of a WeakSet since they are iterable so there is no need to check if the element is being observed for each element in the body. Be carefull to call unobseve in order to avoid memory problems.

like image 97
Isidrok Avatar answered Sep 28 '22 09:09

Isidrok


You just need to set threshold: 1.0 for your Intersection observer. This is a tricky parameter to comprehend. Threshold defines the percentage of the intersection at which the Observer should trigger the callback.

The default value is 0 which means callback will be triggered either when the very first or very last pixel of an element intersects a border of the capturing frame. Your element never completely leaves the capturing frame. This is why callback is never called.

If we set the threshold to 1 we tell the observer to trigger our callback when the element is 100% within the frame. It means the callback will be triggered on change in this state of 100% inclusiveness. I hope that sounds understandable :)

// elements
let green = document.querySelector('#green');
let red = document.querySelector('#red');

// observer callback
let callback = entries => {
  entries.forEach(entry => {
    let isInside = entry.intersectionRatio >= 1 ? "fully" : "NOT";
    console.log("#" + entry.target.id + " is " + isInside + " inside #container");
  });
};

// start observer
let options = {root: document.querySelector('#container'), threshold: 1.0 };
let observer = new IntersectionObserver(callback, options);
observer.observe(green);
observer.observe(red);

// button action
document.querySelector('button').addEventListener('click', () => {
  red.style.right = red.style.right == "" ? "0px" : "";
});
#container {
  width: 100px;
  height: 100px;
  background: blue;
  position: relative;
}

#green, #red {
  width: 50px;
  height: 50px;
  background: green;
  position: absolute;
}

#red {
  background: red;
  right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
  <div id="green"></div>
  <div id="red"></div>
</div>
like image 44
Georgy Nemtsov Avatar answered Sep 28 '22 09:09

Georgy Nemtsov