Find element that's on the middle of the visible screen (viewport) to target another element

65
March 16, 2022, at 3:10 PM

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
});
}

Would love all the help I could get. Cheers
Answer 1
Using the IntersectionObserver API

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>

  • Use data-* Attribute on your articles Elements where the value should match the selector of the related picture.
  • Toggle a class i.e: .is-active using classList that will determine the active styles for the matching picture Element
  • To make the fixed effect use position: sticky on your pictures parent element.
READ ALSO
How to trigger child elements continuous

How to trigger child elements continuous

depending on which container I click, I would like to add a class to the equal boxSo if I click on the first container -> add class to first box

67
Check checkbox by index in another list

Check checkbox by index in another list

I have an unordered list with div and a hidden checkbox list, each with the same number of items

100
Skill stats are changing in a weird way

Skill stats are changing in a weird way

My problem: skill stats are changing in a weird way

61
How to Get Element By Class in JavaScript?

How to Get Element By Class in JavaScript?

I want to replace the contents within a html element so I'm using the following function for that:

105