Lisa
Lisa

Reputation: 29

How to change the navbar's color on request?

I want to change the navbar color when my specific div is hitting the top of the page. It only does it on my second request and not on my first, even though the console says it is working.

I can't find the reason it only works on the second 'box' of the forEach. Maybe it has something to do with that, but I also wrote it separately and it doesn't work.

const boxes = document.querySelectorAll('.Boxes');
const navContainer = document.querySelector('.nav-container');
const navItems = document.querySelectorAll('.nav-item');
const lines = document.querySelectorAll('.line');

window.addEventListener('scroll', function() {
  boxes.forEach((box) => {
    let boxRect = box.getBoundingClientRect();

    if (boxRect.top <= 100 && boxRect.bottom >= 100) {
      navContainer.style.backgroundColor = "#6a994e";
      //console.log(navContainer.style.backgroundColor);
      navItems.forEach((item) => {
        item.style.color = "white";
      })
      lines.forEach((line) => {
        line.style.backgroundColor = "white";
      })
    } else {
      navContainer.style.backgroundColor = "white";
      navItems.forEach((item) => {
        item.style.color = "#6a994e";
      })
      lines.forEach((line) => {
        line.style.backgroundColor = "#6a994e";
      })
    }
  })
});
body{
    margin: 0;
    padding: 0;
    font-family: Arial;
    overflow-x: clip;
}

.nav-container{
    width: 100%;
    height: 75px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: 75px;
    padding: 10px 50px;
    background-color: white;
    color: black;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 5;
    transition: all 0.5s linear;
}



.menu-container{
    display: inline-grid;
    justify-items: end;
    align-items: center;
}

.icon{
    position: relative;
    width: 40px;
    height: 40px;
    border: none;
    background-color: transparent;
    z-index: 2;
}

.menu-container .icon .line{
    width: 80%;
    height: 2px;
    background-color: #6a994e;
    position: absolute;
    transition: transform 0.6s linear;
}

.menu-container .icon .line1{
    top: 15px;
    left: 5px;
}
.menu-container .icon .line2{
    bottom: 15px;
    left: 5px;
}

.boxes{
    background-color: #6a994e;
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    overflow-x: hidden;
    animation: theater;
    animation-timeline: view();
    
}


@keyframes theater{
    0%{
        transform: scale(1);
    } 50% {
        transform: scale(2);
    }
}
<nav>
        <div class="nav-container">
            <a href=""><i class="fa-solid fa-paw nav-item"></i></a>
            <h1 class="nav-item">LOGO</h1>
            <div class ="menu-container">
                <button class="icon" id="toggle">
                    <div class="line line1"></div>
                    <div class="line line2"></div>
                </button>
                <ul class="nav-list">
                    <li><a href="">Home</a></li>
                    <li><a href="">Gallerij</a></li>
                    <li><a href="">Contact</a></li>
                </ul>
            </div>
        </div>
    </nav>

<section>
        <div class="boxes">
            <h1>text</h1>
        </div>
    </section>
  
  
<section>
        <div class="boxes">
            <h1>text</h1>
        </div>
    </section>

Upvotes: 1

Views: 67

Answers (2)

zer00ne
zer00ne

Reputation: 44088

The example below does the following:

  • When the user scrolls a <section class="important"> past the bottom border of <nav>, the color scheme of <nav> reverses from green on white to white on green.

At first I was trying to implement Intersection Observer API but it wasn't really designed well for multiple targets. Having to resort to using the old way of using .getClientBoundingRect(), I tried to minimize the calculations so there wouldn't be a need for debouncing.

You'll notice that everything has drastically changed:

  1. Everything is responsive. It's far from perfect, in full view it is too large, the :root font-size needs clamping.

  2. Content was added (Samuel L. Jackson and Breaking Bad ipsums) because any layout with scrolling problems needs actual content to scroll.

  3. Functionality and aesthetics were added to the dropdown menu and elsewhere. The hamburger needs adjusting for larger screens (see #1).

The only thing I didn't change was the color scheme, but I recommend that you change it so the contrast is better. Review this contrast assessment which only has a contrast ratio of 3.34:1.

Details are commented in the example.

// Reference <html>
const root = document.documentElement;
// Reference <nav>
const nav = document.querySelector("nav");
// Reference <summary>
const btn = document.querySelector("summary");
// An array of all <section>s
const sections = [...document.querySelectorAll("section")];

// Counter index for sections array
let idx = 0;
// Saved reference for scroll position
let y1 = 0;
// Value for scroll direction -1 = up 1 = down.
let dir = 0;

/**
 * Register <summary> to the "click" event.
 * When the user clicks <summary>, event handler
 * toggle() is called.
 */
btn.addEventListener("click", toggle);

/**
 * "click" event handler switches .active and
 * .cross classes to animate hamburger.
 * @param {object} e - Event object
 */
function toggle(e) {
  this.querySelectorAll(".line").forEach(l => {
    l.classList.toggle("active");
    l.classList.toggle("cross");
  });
}

/**
 * Register the window to the "scroll" event.
 * When the user scrolls the <section>s, the
 * event handler scrolling() is called.
 */
window.addEventListener("scroll", scrolling);

/**
 * "scroll" event handler switches colors on <nav>
 * whenever the user scrolls a section.important
 * past the bottom border of <nav>.
 * @param {object} e - Event object
 */
function scrolling(e) {
  /**
   * Calculate the direction of scrolling
   * dir = -1 up / 1 down.
   * y1 is the old scroll position
   * y2 is the current scroll position
   */
  let y2 = document.body.getBoundingClientRect().top
  dir = y1 < y2 ? -1 : 1;
  y1 = y2;

  // BCR of <nav>
  const bar = nav.getBoundingClientRect();
  // BCR of current <section>
  const sec = sections[idx].getBoundingClientRect();
  /**
   * yTop()    = The top of current <section> - 
   *             The bottom of <nav>
   * yBottom() = The bottom of current <section> -
   *             The bottom of <nav>
   *
   * If yTop() is negative AND
   * yBottom() is positive AND
   * the current <section> has class .important...
   */
  if (yTop(sec, bar) < 0 && 
      yBottom(sec, bar) > 0 && 
      sections[idx].matches(".important")) {
    // switch to white on green color scheme...
    root.style.setProperty("--bkg", "#6a994e");
    root.style.setProperty("--txt", "#fff");
    /**
     * otherwise if yBottom() is negative AND
     * user is scrolling down...
     */
  } else if (yBottom(sec, bar) < 0 && dir === 1) {
    // calculate for the next <section>...
    idx++;
    idx = idx > sections.length - 1 ? sections.length - 1 : idx;
    // switch to green on white...
    root.style.setProperty("--txt", "#6a994e");
    root.style.setProperty("--bkg", "#fff");
    /**
     * otherwise if yTop() is positive AND
     * user is scrolling up...
     */
  } else if (yTop(sec, bar) > 0 && dir === -1) {
    // calculate for the previous <section>...
    idx--;
    idx = idx < 0 ? 0 : idx;
    // switch to green on white...
    root.style.setProperty("--txt", "#6a994e");
    root.style.setProperty("--bkg", "#fff");
    // or just quit.
  } else {
    return;
  }
  // console.log(idx);
}

/**
 * Utility function that calculates the difference
 * between the current <section> top position
 * and <nav> bottom position.
 * @param {number} sec - Current <section>
 *                       .top position
 * @param {number} bar - <nav> .bottom position
 * @return {number}    - Difference between sec 
 *                       and bar
 */
function yTop(sec, bar) {
  return Math.floor(sec.top - bar.bottom);
}

/**
 * Utility function that calculates the difference
 * between the current <section> bottom position
 * and <nav> bottom position.
 * @param {number} sec - Current <section>
 *                       .bottom position
 * @param {number} bar - <nav> .bottom position
 * @return {number}    - Difference between sec 
 *                       and bar
 */
function yBottom(sec, bar) {
  return Math.floor(sec.bottom - bar.bottom);
}
/*
These two custom properties will be switched when
a <section class="important"> crosses the bottom
border of <nav>
*/
:root {
  --txt: #6a994e;
  --bkg: #fff;
  font: 5vmax/1.5 "Segoe UI"
}

body {
  width: 100%;
  margin: 0;
  padding: 0;
  overflow-x: hidden;
}

h1, h2 {
  font-family: "Raleway", serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-style: normal;
  font-variant: small-caps
}

h1 {
  font-size: 1.4rem;
}

h2 {
  margin: 1rem 0 -0.75rem;
  font-size: 1.2rem;
}

nav {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 5;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 96vw;
  height: 3.5rem;
  padding: 0 0.5rem;
  color: var(--txt);
  background: var(--bkg);
  transition: all 0.5s linear;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 60vw;
}

a:link,
a:visited {
  color: var(--txt);
}

a:hover,
a:active {
  color: #91fa00;
}

header i {
  font-size: 1.5rem
}

/*
Roko C. Buljan's answer for <details> animation
https://stackoverflow.com/a/79142029/2813224
*/
details {
  position: absolute;
  right: 0;
  top: calc(50% - 0.5rem);
  width: 30vw;
  interpolate-size: allow-keywords;

  &::details-content {
    transition:
      block-size 1s,
      content-visibility 1s allow-discrete;
    overflow: hidden;
    block-size: 0;
  }

  &[open]::details-content {
    block-size: auto;
  }
}

/*
Modified Jumping Hamburger 
https://codepen.io/Jackthomsonn/pen/rVaodQ
*/
summary {
  list-style: none;
  position: relative;
  z-index: 1;
  width: 1rem;
  height: 1rem;
  border: none;
  background: transparent;
  cursor: pointer;
}

/*
eyecatchUp's answer for removing the triangle
from <summary>
https://stackoverflow.com/a/66814239/2813224
*/
summary::marker,
summary::-webkit-details-marker {
  display: none;
}

.line {
  position: absolute;
  transition: all 0.4s cubic-bezier(0.5, 10, 0.6, 0.1);
}

.line1 {
  top: 0;
}

.line2 {
  bottom: 0;
}

.active,
.cross {
  left: 0;
  right: 0;
  width: 100%;
  height: 3px;
  margin-top: 4px;
  margin-left: auto;
  margin-right: auto;
  background: var(--txt);
}

.cross.line1 {
  top: 30%;
  transform: rotate(45deg);
}

.cross.line2 {
  bottom: 30%;
  transform: rotate(-45deg);
  margin-top: -3px;
}

summary:hover .line {
  background: #91fa00;
}

menu {
  list-style: none;
  padding: 0;
  padding-left: 0.5rem;
  margin: 0;
  background: var(--bkg);
}

menu a {
  text-decoration: none;
}

main {
  width: 96vw;
  margin: 0 auto;
  padding: 40vh 0 65vh;
  background: #6a994e;
}

section {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  width: 100%;
  min-height: 100vh;
  color: #fff;
  background: #6a994e;
}

article {
  padding: 0 1rem;
}
<link href="https://fonts.googleapis.com" rel="preconnect">
<link href="https://fonts.gstatic.com"  rel="preconnect" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Raleway&display=swap" rel="stylesheet">

<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet" crossorigin="anonymous" referrerpolicy="no-referrer" />

<nav>

  <header>
    <a href="#"><i class="fa-solid fa-paw"></i></a>
    <h1>H1 Main Title</h1>
  </header>
  
  <details>
    <summary>
      <div class="line line1 active"></div>
      <div class="line line2 active"></div>
    </summary>
    <menu>
      <li><a href="#">Home</a></li>
      <li><a href="#">Gallery</a></li>
      <li><a href="#">Contact</a></li>
    </menu>
  </details>
  
</nav>

<main>

  <section class="important">
    <h2>H2 Important Article</h2>
    <article>
      <p>The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. And you will know My name is the Lord when I lay My vengeance upon thee.</p>
    </article>
  </section>

  <section>
    <h2>H2 Article</h2>
    <article>
      <p>Normally, both your asses would be dead as fucking fried chicken, but you happen to pull this shit while I'm in a transitional period so I don't wanna kill you, I wanna help you. But I can't give you this case, it don't belong to me. Besides, I've already been through too much shit this morning over this case to hand it over to your dumb ass.</p>
    </article>
  </section>

  <section class="important">
    <h2>H2 Important Article</h2>
    <article>
      <p>Now that there is the Tec-9, a crappy spray gun from South Miami. This gun is advertised as the most popular gun in American crime. Do you believe that shit? It actually says that in the little book that comes with it: the most popular gun in American crime. Like they're actually proud of that shit.</p>
    </article>
  </section>

  <section>
    <h2>H2 Article</h2>
    <article>
      <p>Your bones don't break, mine do. That's clear. Your cells react to bacteria and viruses differently than mine. You don't get sick, I do. That's also clear. But for some reason, you and I react the exact same way to water. We swallow it too fast, we choke. We get some in our lungs, we drown. However unreal it may seem, we are connected, you and I. We're on the same curve, just on opposite ends.</p>
    </article>
  </section>

  <section class="important">
    <h2>H2 Important Article</h2>
    <article>
      <p>He has enough money to last forever. He knows he needs to keep moving. You'll never find him. He's out of the picture. I saved his life, I owed him that, but now he and I are done. Which is exactly what you wanted, isn't it. You've always struck me as a very pragmatic man so if I may, I would like to review options with you. Of which, it seems to me you have two.</p>
    </article>
  </section>

  <section>
    <h2>H2 Article</h2>
    <article>
      <p>Option A, you kill me right here and now. Apparently I've made that very easy for you. You can kill me, no witnesses and then spend the next few weeks or months tracking down Jesse Pinkman and you kill him too. A pointless exercise it seems to me but that is Option A.</p>
    </article>
  </section>

  <section class="important">
    <h2>H2 Important Article</h2>
    <article>
      <p>Any other drugs in the house? Think hard; your freedom depends on it. What about guns, you got any guns in the house? Here's your story - You woke up, you found her, that's all you know. Say it. Say it, please. I woke up I found her that's all I know. Say it. I woke up I found her, that's all I know. Again. Again. Again.</p>
    </article>
  </section>

  <section>
    <h2>H2 Article</h2>
    <article>
      <p>Once you call it in, the people who show up with be with the office of medical investigations. it's primarily who you'll talk to. Police officers may arrive they may not, depends on how busy a morning they're having. Typically ODs are not a high priority call. There's nothing here to incriminate you, so I'd be amazed if you got placed under arrest. However, if you do, you say nothing. You tell them you just want your lawyer. And you call Saul Goodman.</p>
    </article>
  </section>

</main>

Upvotes: 0

mplungjan
mplungjan

Reputation: 178375

I assume you want the navbar to match the boxcolor on load too? If yes, we can simplify and reuse

document.addEventListener('DOMContentLoaded', function() {
  const boxes = document.querySelectorAll('.Boxes');
  const navContainer = document.querySelector('.nav-container');
  const navItems = document.querySelectorAll('.nav-item');
  const lines = document.querySelectorAll('.line');

  function updateNavBar() {
    for (const box of boxes) {
      const boxRect = box.getBoundingClientRect();
      if (boxRect.top <= 100 && boxRect.bottom >= 100) {
        const boxColor = box.getAttribute('data-color');
        navContainer.style.backgroundColor = boxColor;
        // we can actually drop these if all text is white on whatever background
        navItems.forEach((item) => {
          item.style.color = "white";
        });
        lines.forEach((line) => {
          line.style.backgroundColor = "white";
        });
        return; // Exit the function once the matching box is found
      }
    }
  }

  // Run on page load
  updateNavBar();

  // Run on scroll
  window.addEventListener('scroll', updateNavBar);
});
body {
  margin: 0;
  font-family: Arial, sans-serif;
}

.nav-container {
  position: fixed;
  top: 0;
  width: 100%;
  background-color: white;
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 10px 0;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  transition: background-color 0.3s;
  z-index: 1000;
}

.nav-item {
  color: #6a994e;
  text-decoration: none;
  font-weight: bold;
  transition: color 0.3s;
}

.line {
  height: 2px;
  width: 50px;
  background-color: #6a994e;
  transition: background-color 0.3s;
}

.Boxes {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 2em;
  color: white;
}

.box1 {
  background-color: #a2d2ff;
}

.box2 {
  background-color: #ffafcc;
}

.box3 {
  background-color: #bde0fe;
}

.box4 {
  background-color: #ffc8dd;
}
<div class="nav-container">
  <a href="#" class="nav-item">Home</a>
  <a href="#" class="nav-item">About</a>
  <a href="#" class="nav-item">Services</a>
  <a href="#" class="nav-item">Contact</a>
  <div class="line"></div>
</div>

<div class="Boxes box1" data-color="#a2d2ff">Section 1</div>
<div class="Boxes box2" data-color="#ffafcc">Section 2</div>
<div class="Boxes box3" data-color="#bde0fe">Section 3</div>
<div class="Boxes box4" data-color="#ffc8dd">Section 4</div>

Upvotes: 2

Related Questions