1

I have a dark-colored menu that sometimes crosses over sections with the same dark background, so am trying to switch its class to change its colors everytime it crosses over dark colored sections.

$(window).scroll(function(){
 var fixed = $("section.fixed-header");

 var fixed_position = $("section.fixed-header").offset().top;
 var fixed_height = $("section.fixed-header").height();

 var toCross_position = $(".dark").offset().top;
 var toCross_height = $(".dark").height();

 if (fixed_position + fixed_height  < toCross_position) {
   fixed.removeClass('light-menu');
 } else if (fixed_position > toCross_position + toCross_height) {
   fixed.removeClass('light-menu');
 } else {
   fixed.addClass('light-menu');
 }

});

This works fine when I only have one div with the dark class inside the same page. However, if there are several different divs with the dark class inside the same page, it will only work for the first div. How could I include all the other divs with the same dark class in here?

Rojo
  • 2,749
  • 1
  • 13
  • 34
  • Possible duplicate of [Change style header/nav with Intersection Observer (IO)](https://stackoverflow.com/questions/57834100/change-style-header-nav-with-intersection-observer-io) – cloned Oct 12 '19 at 16:01

2 Answers2

2

Instead of listening to scroll event you should have a look at Intersection Observer (IO). This was designed to solve problems like yours. And it is much more performant than listening to scroll events and then calculating the position yourself.

Of course you can continue using just scroll events, the official Polyfill from W3C uses scroll events to emulate IO for older browsers. Listening for scroll event and calculating position is not performant, especially if there are multiple elements. So if you care about user experience I really recommend using IO. Just wanted to add this answer to show what the modern solution for such a problem would be.

I took my time to create an example based on IO, this should get you started.

Basically I defined two thresholds: One for 20 and one for 90%. If the element is 90% in the viewport then it's save to assume it will cover the header. So I set the class for the header to the element that is 90% in view.

Second threshold is for 20%, here we have to check if the element comes from the top or from the bottom into view. If it's visible 20% from the top then it will overlap with the header.

Adjust these values and adapt the logic as you see.

Edit: Edited it according to your comment, please note that you may see the effect better if you remove the console.log from my code so they don't clutter up your view.

I added one div where the header doesn't change (the green one)

const sections = document.querySelectorAll('.menu');
const config = {
  rootMargin: '0px',
  threshold: [.2, .9]
};

const observer = new IntersectionObserver(function (entries, self) {
  entries.forEach(entry => {
    console.log(entry); // log the IO entry for demo purposes
    console.log(entry.target); // here you have the Element itself. 
    // you can test for className here for example 
    if (entry.isIntersecting) {
      var headerEl = document.querySelector('header');
      if (entry.intersectionRatio > 0.9) {
        //intersection ratio bigger than 90%
        //-> set header according to target
        headerEl.className=entry.target.dataset.header;      
      } else {
        //-> check if element is coming from top or from bottom into view
        if (entry.target.getBoundingClientRect().top < 0 ) {
          headerEl.className=entry.target.dataset.header;
        }
      } 
    }
  });
}, config);

sections.forEach(section => {
  observer.observe(section);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

.g-100vh {
height: 100vh
}

header {
  min-height: 50px;
  position: fixed;
  background-color: green;
  width: 100%;
}
  
header.white-menu {
  color: white;
  background-color: black;
}

header.black-menu {
  color: black;
  background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<header>
 <p>Header Content </p>
</header>
<div class="grid-30-span g-100vh menu" style="background-color:darkblue;" data-header="white-menu">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_darkblue.jpg" 
    class="lazyload"
    alt="<?php echo $title; ?>">
</div>

<div class="grid-30-span g-100vh no-menu" style="background-color:green;" data-header="black-menu">
    <h1> Here no change happens</h1>
    <p>it stays at whatever state it was before, depending on if you scrolled up or down </p>
</div>

<div class="grid-30-span g-100vh menu" style="background-color:lightgrey;" data-header="black-menu">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_lightgrey.jpg" 
    class="lazyload"
    alt="<?php echo $title; ?>">
</div>
cloned
  • 6,346
  • 4
  • 26
  • 38
  • Thank you for taking the time, I was having a difficult time understanding the other answers' details but with this sample it is much clearer. Where should look for to create a rule so that it affects only the crossings with sections of a certain class? – Golden Pony Oct 16 '19 at 11:32
  • Multiple ways to do this: In the line where it says ` if (entry.isIntersecting) {` put a `console.log(entry)` you will then get the element it intersects with. Here you can then access element.target.className (or so, don't know from the top of my head, will update snippet with correct info) . Or you create a second observer and set the target class to the correct one. – cloned Oct 16 '19 at 11:39
0

The reason is that however the JQuery selector selects all elements with .dark classes, when you chain the .offset().top or .height() methods on it, it will only save the first one into the variable:

var toCross_position = $(".dark").offset().top;
var toCross_height = $(".dark").height();

You could map all positions and heights into arrays and then you should also make the

var toCross_position = $(".dark").offset().top;
var toCross_height = $(".dark").height();
// only the first div's pos and height:
console.log(toCross_height, toCross_position);

var positions = $('.dark').toArray().map(elem => $(elem).offset().top);
var heights = $('.dark').toArray().map(elem => $(elem).height());

console.log('all .dark positions:', positions);
console.log('all .dark heights:', heights);
.dark {
    background-color: #222;
    color: white;
    margin-bottom: 2rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="dark">Dark</div>
<div class="dark">Dark</div>
<div class="dark">Dark</div>
<div class="dark">Dark</div>

check if your header crosses them by looping through those values.

Peter K.
  • 36
  • 4