How to change the text color of a transparent header on scroll, depending on the div it overlaps

Viewed 291

I have a header with a transparent backgrounsd, and I am trying to get the text of the header to change colour between white and black depending on the background of the div it's overlapping.

So far I have managed to add a class of .color-menu to all the divs where I want the header to be black.

I then have it add a class of .dark-menu to the header when the .color-menu div reaches the top of the page.

The problem is that it only works for the first .colour-menu div. It will change to black when it is in the viewport and back to white for the next div but then when the next .color-menu div gets to the top it doesn't change.

So, it seems like the .each function isn't working but I am not sure how to fix it.

$(window).scroll(function() {
 $('.color-menu').each(function(i){
  var top_of_element = $(".color-menu").offset().top;
  var bottom_of_element = $(".color-menu").offset().top + $(".color-menu").outerHeight();
  var top_of_screen = $(window).scrollTop();

  if ((top_of_screen > top_of_element) && (top_of_screen < bottom_of_element)) {
    $(".header").addClass("dark-menu");
  } else {
    $(".header").removeClass("dark-menu");
  }
 });
});

UPDATE: I have also tried using $(this) but it really throws off when it changes color.

$(window).scroll(function() {
 $('.color-menu').each(function(i){
  var top_of_element = $(this).offset().top;
  var bottom_of_element = $(this).offset().top + $(this).outerHeight();
  var top_of_screen = $(window).scrollTop();

  if ((top_of_screen > top_of_element) && (top_of_screen < bottom_of_element)) {
    $(".header").addClass("dark-menu");
  } else {
    $(".header").removeClass("dark-menu");
  }
});
});

Here is a simplified version of my code as an example:

$(document).ready(function() {
  $(".white").addClass("color-menu");
  $(".white-bold").addClass("color-menu");
  $(".light").addClass("color-menu");
  $(".light-bold").addClass("color-menu");
  $(".bright").addClass("color-menu");
});

$(window).scroll(function() {
  $('.color-menu').each(function(i){
    var top_of_element = $(".color-menu").offset().top;
    var bottom_of_element = $(".color-menu").offset().top + $(".color-menu").outerHeight();
    var top_of_screen = $(window).scrollTop();

    if ((top_of_screen > top_of_element) && (top_of_screen < bottom_of_element)) {
      $(".header").addClass("dark-menu");
    } else {
      $(".header").removeClass("dark-menu");
    }
  });
});
.header {
  width: 100%;
  background: rgba(0,0,0,0);
  margin: 0;
  padding:10px;
  position: fixed;
  text-align: center;
}
.header a {
  color: white;
  font-size: 2rem;
  text-transform: uppercase;
}
.dark-menu a{
  color: black;
}
.black {
  background-color: black;
  height: 200px;
}
.white, .white-bold, .light, .light-bold, .bright {
  background-color: white;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="header">
  <a>This is the header</a>
</div>
<div class ="black"></div>
<div class ="white"></div>
<div class ="black"></div>
<div class ="white-bold"></div>
<div class ="black"></div>
<div class ="light"></div>
<div class ="black"></div>
<div class ="light-bold"></div>
<div class ="black"></div>
<div class ="bright"></div>

1 Answers

What is happening in your code is that on scroll, you loop through every color-menu div and add the class if it is the current one... but then the code continues to loop though the remaining elements in the array and removes it again because the page is not in the other div.

I've explained step-by-step the changes you need to get this to work after the example, but first you can see it working here:

Working Example:

$(document).ready(function() {
  $(".white").addClass("color-menu");
  $(".white-bold").addClass("color-menu");
  $(".light").addClass("color-menu");
  $(".light-bold").addClass("color-menu");
  $(".bright").addClass("color-menu");

  $(window).scroll(function() {

    var inColorMenu = false;            /* initialise var to store if we are in color-menu */
    var top_of_screen = $(window).scrollTop();          /* just get this once outside loop */

    /* Loop through each color-menu element and check if we are in one */
    $('.color-menu').each(function(i) {
      var top_of_element = $(this).offset().top;
      var bottom_of_element = top_of_element + $(this).outerHeight();

      /* if we are in a color-menu element, set our var to true and stop processing */
      if ((top_of_screen > top_of_element) && (top_of_screen < bottom_of_element)) {
        inColorMenu = true;
        return false;         /* N.B. need to return "false" to break from the "each" loop */
      }
    });

    if (inColorMenu) {
      $(".header").addClass("dark-menu");
    } else {
      $(".header").removeClass("dark-menu");
    }
  });
});
.header {
  width: 100%;
  background: rgba(0, 0, 0, 0);
  margin: 0;
  padding: 10px;
  position: fixed;
  text-align: center;
}

.header a {
  color: white;
  font-size: 2rem;
  text-transform: uppercase;
}

.header.dark-menu a {
  color: black;
}

.black {
  background-color: black;
  height: 200px;
}

.white,
.white-bold,
.light,
.light-bold,
.bright {
  background-color: white;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="header">
  <a>This is the header</a>
</div>
<div class="black"></div>
<div class="white"></div>
<div class="black"></div>
<div class="white-bold"></div>
<div class="black"></div>
<div class="light"></div>
<div class="black"></div>
<div class="light-bold"></div>
<div class="black"></div>
<div class="bright"></div>

How this works:

  1. Declare a variable to record whether we are in a "color-menu" class or not, and initialise this to false, e.g.:
var inColorMenu = false;

  1. When looping through $('.color-menu').each, if we are between the top and bottom of one of divs (which your code is already detecting), then set our variable to true to record this.
    We can also return false to break the each loop and stop processing the rest of the elements (it will still work without this, we are just reducing the amount of processing required):
if ((top_of_screen > top_of_element) && (top_of_screen < bottom_of_element)) {
    inColorMenu = true;
    return false;         /* N.B. need to return "false" to break from the "each" loop */
}

  1. Finally, after we finish our $('.color-menu').each loop, if inColorMenu is true, we know we are in a color-menu div so we add the dark-menu class to the header, otherwise we remove it:
if (inColorMenu) {
    $(".header").addClass("dark-menu");
} else {
    $(".header").removeClass("dark-menu");
}

  1. Note: You need to use $(this) when getting the offset().top and outerHeight() so that you are getting the values for the current element in the loop. $(".color-menu") gets the values for an unspecified element with this class so will not work.
Related