Reputation: 819
What I am aiming to achieve is something that looks like this. you can see it in action on this URL, if you scroll a bit.
I was thinking at first to try using inViewport, and every time a heading or a paragraph is in viewport to show one image and hide the previous. but my problem is that the elements are in viewport in conjunction
This is the initial code I was using:
$.fn.isInViewport = function () {
let elementTop = $(this).offset().top;
let elementBottom = elementTop + $(this).outerHeight();
let viewportTop = $(window).scrollTop();
let viewportBottom = viewportTop + window.innerHeight; // <-- here
return elementBottom > viewportTop && elementTop < viewportBottom;
};
$(window).scroll(function () {
if ($('.heading1 ').isInViewport()) {
// Use .blogcard instead of this
$('.img1').addClass('show');
} else {
// Remove class
$('.img1').removeClass('show');
}
if ($('.heading2 ').isInViewport()) {
// Use .blogcard instead of this
$('.img2').addClass('show');
} else {
// Remove class
$('.img2').removeClass('show');
}
});
I have found this answer but have no idea on how can I use is to my benefits.
I have also stumbled upon this solution, which looks even smarter, but the code adds the classes to the selector in viewport and not to a different element.
This is the code applied there:
ar getElementsInArea = (function(docElm){
var viewportHeight = docElm.clientHeight;
return function(e, opts){
var found = [], i;
if( e && e.type == 'resize' )
viewportHeight = docElm.clientHeight;
for( i = opts.elements.length; i--; ){
var elm = opts.elements[i],
pos = elm.getBoundingClientRect(),
topPerc = pos.top / viewportHeight * 100,
bottomPerc = pos.bottom / viewportHeight * 100,
middle = (topPerc + bottomPerc)/2,
inViewport = middle > opts.zone[1] &&
middle < (100-opts.zone[1]);
elm.classList.toggle(opts.markedClass, inViewport);
if( inViewport )
found.push(elm);
}
};
})(document.documentElement);
////////////////////////////////////
// How to use:
window.addEventListener('scroll', f)
window.addEventListener('resize', f)
function f(e){
getElementsInArea(e, {
elements : document.querySelectorAll('div'),
markedClass : 'highlight--1',
zone : [20, 20] // percentage distance from top & bottom
});
getElementsInArea(e, {
elements : document.querySelectorAll('div'),
markedClass : 'highlight--2',
zone : [40, 40] // percentage distance from top & bottom
});
}
Upvotes: 3
Views: 1442
Reputation: 206459
It should be quite simple by using the IntersectionObserver API to watch for your elements intersecting the viewport, or any other (Options root
) ancestor.
To detect an element reaches the viewport vertical center can be done by passing the Option rootMargin
where the bottom and top values are set at -50%
, with an Option threshold
set to 0
(as soon as one pixel enters that intersecting area)
// Utility functions:
const EL = (sel, par) => (par || document).querySelector(sel);
const ELS = (sel, par) => (par || document).querySelectorAll(sel);
// App:
const ELS_pictures = ELS(".picture");
const switchPicture = (EL_entry) => {
const EL_picTarg = EL(EL_entry.dataset.reveal);
ELS_pictures.forEach(EL_pic => EL_pic.classList.toggle("is-active", EL_pic === EL_picTarg));
};
// In Viewport
const inViewport = (entries, observer) => entries.forEach(entry => entry.isIntersecting && switchPicture(entry.target));
// Assign observer to all Elements with data-reveal attribute (Articles)
ELS("[data-reveal]").forEach(el => {
const observer = new IntersectionObserver(inViewport, {
// root: (by default is Document),
rootMargin: "-50% 0px -50% 0px", // set the root intersecting area as a tiny line in the vertical center
threshold: 0, // 0 = as soon as 1px intersects
});
observer.observe(el);
});
/* QuickReset */
* {
margin: 0;
box-sizing: border-box;
}
body {
font: 18px/1.5 sans-serif;
}
.stickers {
display: grid;
grid-template-columns: 2fr 3fr;
max-width: 1000px;
margin: 0 auto;
}
/* Article Component */
.articles {
display: flex;
flex-direction: column;
}
.article {
margin: 100px 0;
padding: 30px;
}
.pictures {
position: sticky;
display: flex;
top: 0px;
right: 0;
height: 100vh;
background: #444;
}
.picture {
position: absolute;
height: 100%;
width: 100%;
object-fit: cover;
margin: auto;
transition: 0.3s opacity, 0.5s transform;
opacity: 0;
transform: scale(0.8);
}
.picture.is-active {
opacity: 1;
transform: scale(1);
}
<p style="height: 80vh;">Scroll down...</p>
<div class="stickers">
<div class="articles">
<div class="article" data-reveal="#picture_1">
<h1>Many cats</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Optio laudantium perferendis eius iusto vitae eaque, eligendi ullam, rerum maiores, totam velit! Debitis repudiandae aliquam placeat, minus. Facere nihil aspernatur nam!</p>
</div>
<div class="article" data-reveal="#picture_2">
<h1>Little cat</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur perferendis possimus asperiores deleniti voluptatum amet nostrum ratione odio, a perspiciatis suscipit ab nulla repellat laudantium praesentium adipisci! Nihil ex, quos!</p>
</div>
<div class="article" data-reveal="#picture_3">
<h1>Snowcat</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur perferendis possimus asperiores deleniti voluptatum amet nostrum ratione odio, a perspiciatis suscipit ab nulla repellat laudantium praesentium adipisci! Nihil ex, quos!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur perferendis possimus asperiores deleniti voluptatum amet nostrum ratione odio, a perspiciatis suscipit ab nulla repellat laudantium praesentium adipisci! Nihil ex, quos!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur perferendis possimus asperiores deleniti voluptatum amet nostrum ratione odio, a perspiciatis suscipit ab nulla repellat laudantium praesentium adipisci! Nihil ex, quos!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur perferendis possimus asperiores deleniti voluptatum amet nostrum ratione odio, a perspiciatis suscipit ab nulla repellat laudantium praesentium adipisci! Nihil ex, quos!</p>
</div>
<div class="article" data-reveal="#picture_4">
<h1>Cat</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut recusandae doloribus laboriosam, quasi aspernatur modi illum voluptate dicta alias optio, omnis qui deserunt. Quisquam, beatae dolores cum nostrum sint minima!</p>
</div>
</div>
<div class="pictures">
<img class="picture is-active" id="picture_1" src="https://placekitten.com/380/300" alt="Catz!">
<img class="picture" id="picture_2" src="https://placekitten.com/460/400" alt="Catz!">
<img class="picture" id="picture_3" src="https://placekitten.com/400/450" alt="Catz!">
<img class="picture" id="picture_4" src="https://placekitten.com/500/450" alt="Catz!">
</div>
</div>
<p style="height: 180vh;">Etc...</p>
data-*
Attribute on your articles Elements where the value should match the selector of the related picture..is-active
using classList that will determine the active styles for the matching picture Elementposition: sticky
on your pictures parent element.Upvotes: 3