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 ment 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=".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>
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 ment 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./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>
Share
Improve this question
edited May 17, 2020 at 15:24
doğukan
27.7k13 gold badges63 silver badges75 bronze badges
asked May 15, 2020 at 8:33
Jascha GoltermannJascha Goltermann
1,1244 gold badges16 silver badges35 bronze badges
3
- You should really use Intersection Observer for this. This was designed to watch elements whenever they e into view and react to it. – cloned Commented May 15, 2020 at 10:05
- Hi, thanks for the tip! I have really no idea what that is and how to use this. I was only able to put together the code above with a lot of help from others - I wouldn't be able to write or understand it myself.. Can you show me? – Jascha Goltermann Commented May 15, 2020 at 11:15
- I have found this but I can't make it work for my code: jsfiddle/sublines/xcyaks4g/5 – Jascha Goltermann Commented May 15, 2020 at 11:36
1 Answer
Reset to default 5 +50The fiddle you provided works fine with few changes. You need to apply the observer to all elements for it to work.
See this example:
const config = {
root: null,
rootMargin: '0px',
threshold: [0.1, 0.5, 0.7, 1]
};
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 images = document.querySelectorAll('.box');
images.forEach(image => {
observer.observe(image);
});
.box {
width: 100%;
height: 50vh;
margin-bottom: 10px;
background: lightblue;
transition: opacity .2s ease;
display: flex;
justify-content: center;
align-items: center;
font-size: 3em;
}
[class*='inview'] {
opacity: 1;
}
[class*='outview'] {
opacity: 0.6;
}
<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>