Reputation: 315
I am running into a problem where my intersection observer has two entries both of whose targets are identical but their intersectionRatio is different at the same time in the app. Why could this be? Since it's the same element and the same viewport, shouldn't the intersectionRatio be the same? I am scrolling continuously in the app using .scrollTo()
. Is it possible that the element is temporarily removed from the DOM and put back in which is resulting in the two entries?
I am using the IntersectionObserver as:
const intersectionObserverOptions = {
root: rootRef.current,
rootMargin: '0px',
threshold: 0.01
};
const observer = new IntersectionObserver(
intersectionObserverCallback,
intersectionObserverOptions
);
observer.observe(myElement);
Upvotes: 3
Views: 632
Reputation: 137016
It may happen if you lock the event loop in the task right after the rendering of the first intersection, but before the callback actually fires.
For instance, this snippet will reproduce it:
setTimeout(() => { // avoid Firefox's weird setTimeout priority at page load
const target = document.querySelector(".target");
const observer = new IntersectionObserver((entries) => {
console.log(entries.length);
console.log(entries.map((e) => e.isIntersecting));
});
observer.observe(target); // register in this frame
requestAnimationFrame(() => {
setTimeout(() => { // after paint
target.style.display = "block";
const t1 = performance.now();
while (performance.now() - t1 < 300) {
// lock the event loop
}
console.log("after lock"); // Will be output before the intersection callback fires
})
});
}, 100)
.target {
display: none
}
<div class=target></div>
What happens here is that the intersection observer task is queued at the end of the update the rendering algorithm (current step 18), which is after the requestAnimationFrame
callbacks are all executed. So our timer is queued first (maybe it also has higher priority, I didn't check that) and when it is called, it will lock the event loop enough so that at the end of the busy loop the update the rendering would happen again, once again before our intersection observer task because it has less priority.
And now the observer has two entries in its queue, the initial one, before the busy loop, and the one after the busy loop.
As you can see from your logs in the screenshot, there is a 55ms difference between both your entries' time
values. So you certainly faced something of the sort. If it happens regularly, check your dev tools's performance panel for long frames and try to trace where they come from.
Since it's been asked, below is another snippet, triggered by scrolling: Scroll fast enough to reach the top or bottom of the page before the deadlock ends.
const target = document.querySelector(".target");
const observer = new IntersectionObserver((entries) => {
console.log(entries.length);
})
observer.observe(target);
onscroll = evt => { // Fires before IO callbacks are queued
// Queue a task that will lock the EL after the first IO callback has been queued
setTimeout(() => {
const { top, bottom } = target.getBoundingClientRect();
if (top <= innerHeight && bottom >= 0) {
const t1 = performance.now();
while (performance.now() - t1 < 600) {}
}
});
}
.pusher {
height: 200vh;
background-image: linear-gradient(red, blue);
}
<div class="pusher"></div>
<div class="target"></div>
<div class="pusher"></div>
Upvotes: 4