I have a piece of code that adds a different css class to elements if scrolled into view from top and bottom.
To achieve this, the code recognises four states:
- Scrolled into view from top (adds class "inview-top")
- Scrolled into view from bottom (adds class "inview-bottom")
- Scrolled out of view at the top (adds class "outview-top")
- Scrolled out of view at the bottom (adds class "outview-top")
It also removes any inview classes when adding outview classes and vice versa.
My problem is that it uses the Intersection Observer API to achieve this and it seems to be super unreliable. It works perfectly when the observed elements are only below each other but when they are next to each other in one row, it becomes extremely buggy. Many times it does not fire the callback at all. In my example this means that most DIVs stay invisible even though they should become visible as soon as they're scrolled into view.
That is why I would like to know a reliable method to achieve the desired result. It should perform well no matter how many elements are on a page and no matter where they are placed.
You can try my code on jsFiddle or see it here:
const config = {
root: null,
rootMargin: '0px',
threshold: [0.15, 0.2, 0.25, 0.3]
};
let previousY = 0;
let previousRatio = 0;
let observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
const currentY = entry.boundingClientRect.y
const currentRatio = entry.intersectionRatio
const isIntersecting = entry.isIntersecting
const element = entry.target;
element.classList.remove("outview-top", "inview-top", "inview-bottom", "outview-bottom");
// Scrolling up
if (currentY < previousY) {
const className = (currentRatio >= previousRatio) ? "inview-top" : "outview-top";
element.classList.add(className);
// Scrolling down
} else if (currentY > previousY) {
const className = (currentRatio <= previousRatio) ? "outview-bottom" : "inview-bottom";
element.classList.add(className);
}
previousY = currentY
previousRatio = currentRatio
})
}, config);
const viewbox = document.querySelectorAll('.viewme');
viewbox.forEach(image => {
observer.observe(image);
});
body {
text-align: center;
}
.hi {
padding: 40vh 0;
background: lightblue;
}
.box {
width: 23%; /* change this to 100% and it works fine */
height: 40vh;
margin-bottom: 10px;
background: blue;
display: inline-block;
}
.viewme {
opacity: 0;
transform: translateY(20px);
transition: all .3s ease;
}
.inview-top, .inview-bottom {
opacity: 1;
transform: translateY(0);
}
.outview-top {
opacity: 0;
transform: translateY(-20px);
}
.outview-bottom {
opacity: 0;
transform: translateY(20px);
}
<p class="hi">There should always be four blue boxes in one row. Scroll down and back up</p>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>
<div class="box viewme"></div>