JSFiddle - React, Tailwind, and code Playground
by Hugo Vale Pereira
HTML
<p class="hi">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>
<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>
<div class='margins'></div>
CSS
html, body {
text-align: center;
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
overflow: auto;
}
.margins {
position: fixed;
top: 100px;
bottom: 100px;
border-top: 2px dashed;
border-bottom: 2px dashed;
z-index: 1;
left: 0;
width: 100%;
pointer-events: none;
}
.hi {
padding: 40vh 0;
background: lightgray;
}
.box {
width: 23%;
min-width: 100px;
height: 130vh;
margin-bottom: 10px;
background: lightblue;
display: inline-block;
}
.viewme {
transition: all .3s ease;
}
.viewme[data-view='inview-top'],
.viewme[data-view='inview-bottom'] {
opacity: 1;
transform: translateY(0);
}
.viewme[data-view='outview-top'] {
opacity: 0;
transform: translateY(-20px);
}
.viewme[data-view='outview-bottom'] {
opacity: 0;
transform: translateY(20px);
}
JavaScript
const config = {
// Add root here so rootBounds in entry object is not null
root: document.body,
// Margin to when element should take action
rootMargin: '-100px 0px',
// Callback will be fired 30 times during intersection
threshold: [...Array(30).keys()].map(x => x / 29)
};
let observer = new IntersectionObserver(function(entries, observer) {
entries.forEach((entry, index) => {
const element = entry.target;
// Get root element (document) coords
const rootTop = entry.rootBounds.top;
const rootBottom = entry.rootBounds.height;
// Get div coords
const topBound = entry.boundingClientRect.top - 100; // margin in config
const bottomBound = entry.boundingClientRect.bottom;
let className;
// Do calculations to get class names
if (topBound < rootTop && bottomBound < rootTop) {
className = "outview-top";
} else if (topBound > rootBottom) {
className = "outview-bottom";
} else if (topBound < rootBottom && bottomBound > rootBottom) {
className = "inview-bottom";
} else if (topBound < rootTop && bottomBound > rootTop) {
className = "inview-top";
}
element.setAttribute('data-view', className);
});
}, config);
const viewbox = document.querySelectorAll('.viewme');
viewbox.forEach(image => {
observer.observe(image);
});