I have a piece of jQuery code that adds a css class to elements when they are scrolled into the viewport and removes that class when they are scrolled out of the viewport.
So far the code works like this:
- When an element is scrolled into the viewport, the class "inview" is added.
- When an element is scrolled out of the viewport, the class "inview" is removed.
So far so good. But what I am trying to achieve is this:
Scrolling into view:
- When an element is scrolled into the viewport from the bottom of the page, the class "inview-bottom" is added.
- When an element is scrolled into the viewport from the top of the page, the class "inview-top" is added.
Scrolling out of view:
- When an element is scrolled out of the viewport from the bottom of the page, the class "outview-bottom" is added.
- When an element is scrolled out of the viewport from the top of the page, the class "outview-top" is added.
Cleaning up:
- When an element is scrolled into the viewport from the top or bottom of the page, all "outview-*" classes should be removed.
- When an element is scrolled out of the viewport from the top or bottom of the page, all "inview-*" classes should be removed.
It was suggested in a comment to use the Intersection Observer API and after reading more about it, I believe it presents the best approach to fulfill the requirements.
Here is my code (open in full page - the preview doesn't work well). You can also find the same code on jsFiddle.
function inView(opt) {
if (opt.selector === undefined) {
console.log('Valid selector required for inView');
return false;
}
var elems = [].slice.call(document.querySelectorAll(opt.selector)),
once = opt.once === undefined ? true : opt.once,
offsetTop = opt.offsetTop === undefined ? 0 : opt.offsetTop,
offsetBot = opt.offsetBot === undefined ? 0 : opt.offsetBot,
count = elems.length,
winHeight = 0,
ticking = false;
function update() {
var i = count;
while (i--) {
var elem = elems[i],
rect = elem.getBoundingClientRect();
if (rect.bottom >= offsetTop && rect.top <= winHeight - offsetBot) {
elem.classList.add('inview');
if (once) {
count--;
elems.splice(i, 1);
}
} else {
elem.classList.remove('inview');
}
}
ticking = false;
}
function onResize() {
winHeight = window.innerHeight;
requestTick();
}
function onScroll() {
requestTick();
}
function requestTick() {
if (!ticking) {
requestAnimationFrame(update);
ticking = true;
}
}
window.addEventListener('resize', onResize, false);
document.addEventListener('scroll', onScroll, false);
document.addEventListener('touchmove', onScroll, false);
onResize();
}
inView({
selector: '.viewme', // an .inview class will get toggled on these elements
once: false, // set this to false to have the .inview class be toggled on AND off
offsetTop: 180, // top threshold to be considered "in view"
offsetBot: 100 // bottom threshold to be considered "in view"
});
.box {
width: 100%;
height: 50vh;
margin-bottom: 10px;
background: blue;
opacity: 0;
transition: opacity .2s ease;
}
.inview {
opacity: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<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>