Reputation: 12452
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>
Upvotes: 29
Views: 8441
Reputation: 46208
A disconnect
did the job for me.
let observer = null
function observe() {
if (observer) observer.disconnect()
observer = new IntersectionObserver(intersectionCallback)
document.querySelectorAll('.element_to_observe').forEach(o => {
if (o) observer.observe(o)
})
}
observe() // \
observe() // > All visible elements are triggered at each call
observe() // /
disconnect
seems to destroy the observer and a new observe
does not duplicate the events
, so it's ok for me.
Upvotes: 0
Reputation: 846
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>
Upvotes: 1
Reputation: 11437
One way to do it is to use MutationObserver. If a mutation happens (in this case style change) call observe/unobserve on the element that has been changed. This way you don't have to do it for all elements.
Here is an example:
// 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);
const boxes = document.querySelectorAll('#container > div');
boxes.forEach(box => {
observer.observe(box);
});
// button action
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
});
// Mutation observer
const targetNode = document.querySelector('#container');
// Options for the observer (which mutations to observe). We only need style changes so we set attributes to true. Also, we are observing the children of container so subtree is true
const config = {
attributes: true,
childList: false,
subtree: true,
attributeFilter: ["style"]
};
// Callback function to execute when mutations are observed
const mutationCallback = (mutationsList, mutationObserver) => {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
observer.unobserve(mutation.target);
observer.observe(mutation.target);
}
}
};
// Create an observer instance linked to the callback function
const mutationObserver = new MutationObserver(mutationCallback);
// Start observing the target node for configured mutations
mutationObserver.observe(targetNode, config);
#container {
width: 300px;
height: 300px;
background: lightblue;
position: relative;
}
#green,
#red {
width: 100px;
height: 100px;
background: green;
position: absolute;
}
#red {
background: purple;
right: -10px;
}
<button>move #red</button>
<br /><br />
<div id="container">
<div id="green"></div>
<div id="red"></div>
</div>
Upvotes: -2
Reputation: 4323
I may not get the question right, what I understand is you want to trigger the IntersectionObserver, so your callback get called. Why don't you call it directly?
document.querySelector('button').addEventListener('click', () => {
red.style.right = red.style.right == "" ? "0px" : "";
callback(red);
});
Upvotes: -1
Reputation: 2191
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.
Upvotes: 1