11

Fiddle latest


I started this question with the scroll event approach, but due to the suggestion of using IntersectionObserver which seems much better approach i'm trying to get it to work in that way.


What is the goal:

I would like to change the style (color+background-color) of the header depending on what current div/section is observed by looking for (i'm thinking of?) its class or data that will override the default header style (black on white).


Header styling:

font-color:

Depending on the content (div/section) the default header should be able to change the font-color into only two possible colors:

  • black
  • white

background-color:

Depending on the content the background-color could have unlimited colors or be transparent, so would be better to address that separate, these are the probably the most used background-colors:

  • white (default)
  • black
  • no color (transparent)

CSS:

header {
  position: fixed;
  width: 100%;
  top: 0;
  line-height: 32px;
  padding: 0 15px;
  z-index: 5;
  color: black; /* default */
  background-color: white; /* default */
}

Div/section example with default header no change on content:

<div class="grid-30-span g-100vh">
    <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_default_header.jpg" 
    class="lazyload"
    alt="">
</div>

Div/section example change header on content:

<div class="grid-30-span g-100vh" data-color="white" data-background="darkblue">
    <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="">
</div>

<div class="grid-30-span g-100vh" data-color="white" data-background="black">
    <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_black.jpg" 
    class="lazyload"
    alt="">
</div>

Intersection Observer approach:

var mq = window.matchMedia( "(min-width: 568px)" );
if (mq.matches) {
  // Add for mobile reset

document.addEventListener("DOMContentLoaded", function(event) { 
  // Add document load callback for leaving script in head
  const header = document.querySelector('header');
  const sections = document.querySelectorAll('div');
  const config = {
    rootMargin: '0px',
    threshold: [0.00, 0.95]
  };

  const observer = new IntersectionObserver(function (entries, self) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        if (entry.intersectionRatio > 0.95) {
          header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
          header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";   
        } else {
        if (entry.target.getBoundingClientRect().top < 0 ) {
          header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
          header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
          }
        } 
      }
    });
  }, config);

  sections.forEach(section => {
    observer.observe(section);
  });

});

}
KP83
  • 616
  • 1
  • 10
  • 34
  • Your question isn't clear enough. `When entering a page and content (div) has header class added it only changes just after scrolling, but it should change the header directly on entering the page.` What color do you expect the header background and text to be when the page loads? When do you expect it to change, and to what color? – Andrew Koster Sep 08 '19 at 16:58
  • `With absolutely none class determined the background of default header turns or stays transparent after scrolling.` I don't understand what that means at all. Please specify the expected behavior and how it differs from the current behavior (especially the colors, since that seems to be the main issue here). – Andrew Koster Sep 08 '19 at 16:59
  • When you open the project-page you see content (mostly images set to 100% width and in 100vh height) in some occasions i want to add a class that suits the content so the header/nav stays visible, but with the code i have now it changes the header/nav into the class only after scrolling – KP83 Sep 08 '19 at 17:19
  • It needs to read the class when opening the page knowing to change the header, after that the scrolling part takes place so when leaving that content it returns to the default header setting black on white background not black on transparent what happens now. – KP83 Sep 08 '19 at 17:29
  • In the fiddle you see an example: the second section (section-grey) that has no class determined but those not return completely to the default header css setting – KP83 Sep 08 '19 at 17:31

5 Answers5

10

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.

First, here is a codepen which shows a solution for your problem. I am not the author of this codepen and I would maybe do some things a bit different but it definitely shows you the basic approach on how to solve your problem.

Things I would change: You can see in the example that if you scoll 99% to a new section, the heading changes even tough the new section is not fully visible.

Now with that out of the way, some explaining on how this works (note, I will not blindly copy-paste from codepen, I will also change const to let, but use whatever is more appropriate for your project.

First, you have to specify the options for IO:

let options = {
  rootMargin: '-50px 0px -55%'
}

let observer = new IntersectionObserver(callback, options);

In the example the IO is executing the callback once an element is 50px away from getting into view. I can't recommend some better values from the top of my head but if I would have the time I would try to tweak these parameters to see if I could get better results.

In the codepen they define the callback function inline, I just wrote it that way to make it clearer on what's happening where.

Next step for IO is to define some elements to watch. In your case you should add some class to your divs, like <div class="section">

let entries = document.querySelectorAll('div.section');
entries.forEach(entry => {observer.observe(entry);})

Finally you have to define the callback function:

entries.forEach(entry => {
    if (entry.isIntersecting) {
     //specify what should happen if an element is coming into view, like defined in the options. 
    }
  });

Edit: As I said this is just an example on how to get you started, it's NOT a finished solution for you to copy paste. In the example based on the ID of the section that get's visible the current element is getting highlighted. You have to change this part so that instead of setting the active class to, for example, third element you set the color and background-color depending on some attribute you set on the Element. I would recommend using data attributes for that.

Edit 2: Of course you can continue using just scroll events, the official Polyfill from W3C uses scroll events to emulate IO for older browsers.it's just that 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.

Edit 3: 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.

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

const observer = new IntersectionObserver(function (entries, self) {
  entries.forEach(entry => {
    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 white-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 black-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
  • First of all thank you for taking the time to help, but the codepen example you just showed me isn't really what i'm after, that works with nav tabs selections i don't need that. Just header / nav to change on class when scrolling and listen to the class when opening page if content has class added. And show default header when no class is added at all. – KP83 Sep 09 '19 at 06:59
  • You don't have to use nav tabs, you can also just change the whole navigation bar. In the codepen it checks for `active` class and depending on which element is visible it highlights the current section. You can just skip this part and change the color depending on the section that get's visible. I will edit this part into my answer. – cloned Sep 09 '19 at 07:02
  • It does not work in my case, all of the navigation what is placed in my header has his color and background determined by just one 'class' (header). In your approach when content is for example dark and i need the header / nav to be white on transparent background it isn't possible, because it listens to data-ref set for each list-item seems to be comprehensive for what i need. – KP83 Sep 09 '19 at 07:13
  • It totally is possible, you can adjust the logic according to your specific needs. You know which element is getting into view and you can react to it however you want. Check it's classname or some other attributes, whatever you need and set your colors accordingly. IO also solves your problem on setting the correct colors on page load if it's already scrolled. – cloned Sep 09 '19 at 07:17
  • When i use the `data-ref="white-menu"` on the content and set id to the whole header / nav instead of each list-item i probably can only use one class (id in this case) to change its appearance, because when set it to data-ref="black-menu"` when i need black on transparent depending on what content it doesn't listen to that because the header already has an id that only listens to white-menu – KP83 Sep 09 '19 at 07:28
  • Honestly I don't get your logic but I don't have to, I know that if it's logical you can program it any way you want. If nothing is set then just fallback to some default values. This would all then happen inside `//specify what should happen ` block. You can even console.log(entry) to see what parameters they have and react accordingly. – cloned Sep 09 '19 at 07:31
  • I appreciate your approach but i don't see it working, it seems comprehensive to me. What i have works for 90% only need a window on load that listens to the class if added showing the change directly not just after scroll and return to default header setting it only returns to the correct font-color but leave the background transparent when it needs to be white when scrolling past content that has class added. – KP83 Sep 09 '19 at 07:41
  • of course, i just added a last edit to my post. I don't think you will use IO, just wanted to tell you what your options are. – cloned Sep 09 '19 at 07:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/199164/discussion-between-kp83-and-cloned). – KP83 Sep 09 '19 at 07:53
  • Completely missed this edit, just seen it now! Don't got it to work on my end yet. Can i just copy paste the js into my main.js or does it need extra syntax for that? – KP83 Sep 28 '19 at 11:20
  • I'm on phone till Monday so short answer for now: did you check this on the desktop? There is a working fiddle with a live demo. I can't tell you where to post your code, do you have more info on that? It's just plain JS so you can put it wherever you like. – cloned Sep 28 '19 at 20:37
  • Checked it on my desktop, but nothing happens – KP83 Sep 29 '19 at 09:53
  • `nothing happens` is not an error description. It works as expected for me when I run the snippet. – cloned Sep 30 '19 at 06:41
  • Must say it isn't the way i had in mind how it should function. The focus now is only on white menu and black menu, but no variables and separate background color declaration. Just want to add if needed a, `font-color` and `background-color` to section/div that needs one depending on the content and change the header like that. I can't give you a error because the console doesn't produced one, but the fact is that nothing works. Placed the script in my `main.js` and put the `data-header="white-menu"`on the first div. – KP83 Sep 30 '19 at 10:04
  • It is almost how it must function despite the combination of the `font-color` and `background-color` together in one `class` not address it separate so can choose any background color by adding `style` so no need to make multiple `classes` for each change in background. Font-color will only be white or black and background also could be transparent in some occasions. Placed the code in codepen and it works in this way, but not local on my computer yet. – KP83 Sep 30 '19 at 10:12
  • You have to extend the example to suit your needs. I just use `headerEl.className=entry.target.dataset.header;` to set data-header from the intersecting element to the headerElement. You can also do more stuff here, even use jQuery. You can set whatever you need/want here. with `entry.target` you get the element that is currently overlapping and with `headerEl` you get the `
    ` You can also use `$(entry.target).css('color', 'black')`.
    – cloned Sep 30 '19 at 10:39
  • First need it to work locally it doesn't react and give any error why... – KP83 Sep 30 '19 at 10:56
  • Playing around in fiddle (see edit) with your code changed some bits for reading and was wondering, is the else statement needed when scrolling back? Can you take a look? Still don't understand why it works in the fiddle but not locally on my computer does the script need begin and ending syntax when using it in a main.js? – KP83 Sep 30 '19 at 11:39
  • There is indeed an edit, added a fiddle link on top. But won't bother you no more... – KP83 Sep 30 '19 at 12:52
  • Just want add: Funny thing is that after all the different IO approaches i starting to get the hang of it. If you see something for the first time is hard to grap the meaning behind every single code line and if it not working on local side hard to understand why, again i'm pretty advanced in CSS and HTML which i learned in the same way trail and error. Same thing would be for you to create something in Cinema 4D etc for example which i know all lot of, but can look overwhelming. – KP83 Sep 30 '19 at 13:02
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/200174/discussion-between-cloned-and-kp83). – cloned Sep 30 '19 at 13:46
  • This solution simply does not work the way it is intended. Tabs do not change correctly when the viewport height is smaller than the section height, and there are numerous other issues as well. – daveycroqet Apr 27 '20 at 21:07
  • This is a starting point. If you want to have sections smaller/bigger than the viewport you have to adapt the code. You can check if a section is coming into the viewport / leaving it and react based on that. Observe what you need and react based on the wanted design outcome. – cloned Apr 28 '20 at 07:31
6

I might not understand the question completely, but as for your example - you can solve it by using the mix-blend-mode css property without using javascript at all.

Example:

header {background: white; position: relative; height: 20vh;}
header h1 {
  position: fixed;
  color: white;
  mix-blend-mode: difference;
}
div {height: 100vh; }
<header>
  <h1>StudioX, Project Title, Category...</h1>
</header>
<div style="background-color:darkblue;"></div>
<div style="background-color:lightgrey;"></div>
KP83
  • 616
  • 1
  • 10
  • 34
A. Meshu
  • 4,053
  • 2
  • 20
  • 34
  • That could be a good idea, but it would be used in a very few cases. If you only want your header to change from black to white for instance, whatever the sections colors are. – Quentin D Oct 27 '20 at 12:14
  • @QuentinD Wasn't that what OP originally tried to do...? – A. Meshu Oct 27 '20 at 18:56
  • "Depending on the content the background-color could have unlimited colors or be transparent" So if the background-color is red, the header couldn't be black or white – Quentin D Oct 28 '20 at 19:05
  • 1
    As i started in this answer: "I might not understand the question completely, but as for your example..." and you can imagine that this question had couple of edits from the moment it was asked - wait, you don't need to imagine - you can click on the curved arrow... Anyway thank you for you comment (and the downvote) - i will try to do my best on future answers. Take care. – A. Meshu Oct 28 '20 at 20:19
3

I've encountered the same situation and the solution I implemented is very precise because it doesn't rely on percentages but on real elements' bounding boxes:

class Header {
  constructor() {
    this.header = document.querySelector("header");
    this.span = this.header.querySelector('span');
    this.invertedSections = document.querySelectorAll(".invertedSection");

    window.addEventListener('resize', () => this.resetObserver());

    this.resetObserver();
  }

  resetObserver() {
    if (this.observer) this.observer.disconnect();

    const {
      top,
      height
    } = this.span.getBoundingClientRect();

    this.observer = new IntersectionObserver(entries => this.observerCallback(entries), {
        root: document,
      rootMargin: `-${top}px 0px -${window.innerHeight - top - height}px 0px`,
    });

    this.invertedSections.forEach((el) => this.observer.observe(el));
  };

  observerCallback(entries) {
    let inverted = false;
    entries.forEach((entry) => {
      if (entry.isIntersecting) inverted = true;
    });
    if (inverted) this.header.classList.add('inverted');
    else this.header.classList.remove('inverted');
  };
}

new Header();
header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: 20px 0;
  text-transform: uppercase;
  text-align: center;
  font-weight: 700;
}
header.inverted {
  color: #fff;
}

section {
  height: 500px;
}
section.invertedSection {
  background-color: #000;
}
<body>
  <header>
    <span>header</span>
  </header>
  <main>
    <section></section>
    <section class="invertedSection"></section>
    <section></section>
    <section class="invertedSection"></section>
  </main>
</body>

What it does is actually quite simple: we can't use IntersectionObserver to know when the header and other elements are crossing (because the root must be a parent of the observed elements), but we can calculate the position and size of the header to add rootMargin to the observer.

Sometimes, the header is taller than its content (because of padding and other stuff) so I calculate the bounding-box of the span in the header (I want it to become white only when this element overlaps a black section).

Because the height of the window can change, I have to reset the IntersectionObserver on window resize.

The root property is set to document here because of iframe restrictions of the snippet (otherwise you can leave this field undefined).

With the rootMargin, I specify in which area I want the observer to look for intersections.

Then I observe every black section. In the callback function, I define if at least one section is overlapping, and if this is true, I add an inverted className to the header.

If we could use values like calc(100vh - 50px) in the rootMargin property, we may not need to use the resize listener.

We could even improve this system by adding side rootMargin, for instance if I have black sections that are only half of the window width and may or may not intersect with the span in the header depending on its horizontal position.

Quentin D
  • 401
  • 6
  • 14
2

@Quentin D

I searched the internet for something like this, and I found this code to be the best solution for my needs.

Therefore I decided to build on it and create a universal "Observer" class, that can be used in many cases where IntesectionObserver is required, including changing the header styles. I haven't tested it much, only in a few basic cases, and it worked for me. I haven't tested it on a page that has a horizontal scroll.

Having it this way makes it easy to use it, just save it as a .js file and include/import it in your code, something like a plugin. :) I hope someone will find it useful.

If someone finds better ideas (especially for "horizontal" sites), it would be nice to see them here.

Edit: I hadn't made the correct "unobserve", so I fixed it.

/* The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

ROOT:
It is not necessary for the root to be the ancestor element of the target. The root is allways the document, and the so-called root element is used only to get its size and position, to create an area in the document, with options.rootMargin.
Leave it false to have the viewport as root.

TARGET:
IntersectionObserver triggers when the target is entering at the specified ratio(s), and when it exits at the same ratio(s).

For more on IntersectionObserverEntry object, see:
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#targeting_an_element_to_be_observed

IntersectionObserverEntry.time               // Timestamp when the change occurred
IntersectionObserverEntry.rootBounds         // Unclipped area of root
IntersectionObserverEntry.intersectionRatio  // Ratio of intersectionRect area to boundingClientRect area
IntersectionObserverEntry.target             // the Element target
IntersectionObserverEntry.boundingClientRect // target.boundingClientRect()
IntersectionObserverEntry.intersectionRect   // boundingClientRect, clipped by its containing block ancestors, and intersected with rootBounds

THRESHOLD:
Intersection ratio/threshold can be an array, and then it will trigger on each value, when in and when out.
If root element's size, for example, is only 10% of the target element's size, then intersection ratio/threshold can't be set to more than 10% (that is 0.1).

CALLBACKS:
There can be created two functions; when the target is entering and when it's exiting. These functions can do what's required for each event (visible/invisible).
Each function is passed three arguments, the root (html) element, IntersectionObserverEntry object, and intersectionObserver options used for that observer.

Set only root and targets to only have some info in the browser's console.

For more info on IntersectionObserver see: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Polyfill: <script src="https://polyfill.io/v3/polyfill.js?features=IntersectionObserver"></script>
or:
https://github.com/w3c/IntersectionObserver/tree/main/polyfill


Based on answer by Quentin D, answered Oct 27 '20 at 12:12
https://stackoverflow.com/questions/57834100/change-style-header-nav-with-intersection-observer-io

root     - (any selector) - root element, intersection parent (only the first element is selected).
targets  - (any selector) - observed elements that trigger function when visible/invisible.
inCb     - (function name) - custom callback function to trigger when the target is intersecting.
outCb    - (function name) - custom callback function to trigger when the target is not intersecting.
thres    - (number 0-1) - threshold to trigger the observer (e.g. 0.1 will trigger when 10% is visible).
unobserve- (bolean) - if true, the target is unobserved after triggering the callback.

EXAMPLE:
(place in 'load' event listener, to have the correct dimensions)

var invertedHeader = new Observer({
   root: '.header--main', // don't set to have the viewport as root
   targets: '[data-bgd-dark]',
   thres: [0, .16],
   inCb: someCustomFunction,
});
*/

class Observer {
   constructor({
      root = false,
      targets = false,
      inCb = this.isIn,
      outCb = this.isOut,
      thres = 0,
      unobserve = false,
   } = {}) {
      // this element's position creates with rootMargin the area in the document
      // which is used as intersection observer's root area.
      // the real root is allways the document.
      this.area = document.querySelector(root); // intersection area
      this.targets = document.querySelectorAll(targets); // intersection targets
      this.inCallback = inCb; // callback when intersecting
      this.outCallback = outCb; // callback when not intersecting
      this.unobserve = unobserve; // unobserve after intersection
      this.margins; // rootMargin for observer
      this.windowW = document.documentElement.clientWidth;
      this.windowH = document.documentElement.clientHeight;

      // intersection is being checked like:
      // if (entry.isIntersecting || entry.intersectionRatio >= this.ratio),
      // and if ratio is 0, "entry.intersectionRatio >= this.ratio" will be true,
      // even for non-intersecting elements, therefore:
      this.ratio = thres;
      if (Array.isArray(thres)) {
         for (var i = 0; i < thres.length; i++) {
            if (thres[i] == 0) {
               this.ratio[i] = 0.0001;
            }
         }
      } else {
         if (thres == 0) {
            this.ratio = 0.0001;
         }
      }

      // if root selected use its position to create margins, else no margins (viewport as root)
      if (this.area) {
         this.iArea = this.area.getBoundingClientRect(); // intersection area
         this.margins = `-${this.iArea.top}px -${(this.windowW - this.iArea.right)}px -${(this.windowH - this.iArea.bottom)}px -${this.iArea.left}px`;
      } else {
         this.margins = '0px';
      }

      // Keep this last (this.ratio has to be defined before).
      // targets are required to create an observer.
      if (this.targets) {
         window.addEventListener('resize', () => this.resetObserver());
         this.resetObserver();
      }
   }

   resetObserver() {
      if (this.observer) this.observer.disconnect();

      const options = {
         root: null, // null for the viewport
         rootMargin: this.margins,
         threshold: this.ratio,
      }

      this.observer = new IntersectionObserver(
         entries => this.observerCallback(entries, options),
         options,
      );

      this.targets.forEach((target) => this.observer.observe(target));
   };

   observerCallback(entries, options) {
      entries.forEach(entry => {
         // "entry.intersectionRatio >= this.ratio" for older browsers
         if (entry.isIntersecting || entry.intersectionRatio >= this.ratio) {
            // callback when visible
            this.inCallback(this.area, entry, options);

            // unobserve
            if (this.unobserve) {
               this.observer.unobserve(entry.target);
            }
         } else {
            // callback when hidden
            this.outCallback(this.area, entry, options);
            // No unobserve, because all invisible targets will be unobserved automatically
         }
      });
   };

   isIn(rootElmnt, targetElmt, options) {
      if (!rootElmnt) {
         console.log(`IO Root: VIEWPORT`);
      } else {
         console.log(`IO Root: ${rootElmnt.tagName} class="${rootElmnt.classList}"`);
      }
      console.log(`IO Target: ${targetElmt.target.tagName} class="${targetElmt.target.classList}" IS IN (${targetElmt.intersectionRatio * 100}%)`);
      console.log(`IO Threshold: ${options.threshold}`);
      //console.log(targetElmt.rootBounds);
      console.log(`============================================`);
   }
   isOut(rootElmnt, targetElmt, options) {
      if (!rootElmnt) {
         console.log(`IO Root: VIEWPORT`);
      } else {
         console.log(`IO Root: ${rootElmnt.tagName} class="${rootElmnt.classList}"`);
      }
      console.log(`IO Target: ${targetElmt.target.tagName} class="${targetElmt.target.classList}" IS OUT `);
      console.log(`============================================`);
   }
}
vIGGS
  • 51
  • 5
0

This still needs adjustment, but you could try the following:

const header = document.getElementsByTagName('header')[0];

const observer = new IntersectionObserver((entries) => {
 entries.forEach((entry) => {
    if (entry.isIntersecting) {
       header.style.color = entry.target.dataset.color || '';
        header.style.backgroundColor = entry.target.dataset.background;
    }
  });
}, { threshold: 0.51 });

[...document.getElementsByClassName('observed')].forEach((t) => {
    t.dataset.background = t.dataset.background || window.getComputedStyle(t).backgroundColor;
    observer.observe(t);    
});
body {
  font-family: arial;
  margin: 0;
}

header {
  border-bottom: 1px solid red;
  margin: 0 auto;
  width: 100vw;
  display: flex;
  justify-content: center;
  position: fixed;
  background: transparent;  
  transition: all 0.5s ease-out;
}

header div {
  padding: 0.5rem 1rem;
  border: 1px solid red;
  margin: -1px -1px -1px 0;
}

.observed {
  height: 100vh;
  border: 1px solid black;
}

.observed:nth-of-type(2) {
  background-color: grey;
}

.observed:nth-of-type(3) {
  background-color: white;
}
<header>
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
</header>

<div class="observed">
  <img src="http://placekitten.com/g/200/300">
  <img src="http://placekitten.com/g/400/300">
</div>
  
<div class="observed" data-color="white" data-background="black">
  <img src="http://placekitten.com/g/600/300">
</div>

<div class="observed" data-color="black" data-background="white">
  <img src="http://placekitten.com/g/600/250">
</div>

The CSS ensures each observed section takes up 100vw and the observer does its thing when anyone of those comes into view by at least 51% percent.

In the callback the headers background-color is then set to the background-color of the intersecting element.

Andre Nuechter
  • 2,141
  • 11
  • 19
  • Thank you for answering this still pending question. I just tried your code, but it doesn't work. Also here in the example it doesn't return to the default header when scrolling back. The approach seems the way i had explained, to address the color and background separate – KP83 Sep 27 '19 at 08:00
  • It not returning to its default state is due to the times it fires - whenever one of the targets comes into view by at least 50% (the threshold). Which means the last time it fired before scrolling back was on the second div. If you insist on using an intersectionobserver, you need to fiddle around with the config, BUT I suspect a listener on scroll, as you intended initially, would be more suited to your goal as a fixed header is always intersecting. – Andre Nuechter Sep 27 '19 at 15:42
  • After the user: *cloned* told me about IO got more information about it and it is a more modern approach than the old listener on scroll function which as his downfalls, but even if it was it didn't work exactly as i wanted. Fixed header does not have any issues of intersecting al the time, that isn't true or the case. There lots of examples of changing a fixed header on scroll using intersection observer: https://youtu.be/RxnV9Xcw914 – KP83 Sep 27 '19 at 15:59
  • As I've understood your goal, you want to change the styling of your header when the user has scrolled down. The problem I tried to point out is with choosing the target: the document-root is always intersecting and any other element will have finicky behavior as in my example. You might try to add an invisible alias-element beneath the header, but I think it would be much easier to watch the yOffset. The performance issues should be negligible on a small project like a portfolio. – Andre Nuechter Sep 27 '19 at 16:10
  • It isn't always the case of changing on scroll sometimes the header keeps his default state. So for example the project has a blue background i would like to add `white-menu` and `background-color="blue"` or keep it transparent it depends on the project content. So when scrolling down the content (images of video) each of those will tell the header what it should be when intersecting or do nothing and keep the default header. – KP83 Sep 27 '19 at 16:20
  • Which good be done with `data-color` or `data-background` approach by telling it what is should be or with a `white-menu` class and simple `style=background-color: ...;"` – KP83 Sep 27 '19 at 16:24
  • I think it is a rather flexible approach, but you can do it any way you like. I edited the snippet a bit to maybe better reflect what you're trying (try it on full screen). – Andre Nuechter Sep 27 '19 at 16:29
  • But still ain't got nowhere, appreciate the help, but don't see it working on my end – KP83 Sep 27 '19 at 16:31
  • Also main thing about IO is that is works without having to scroll first. So when landing on the page and the header needs to change it already is changed and not just after scrolling so that is the mean reason for IO approach compare it to scroll event what doesn't do that. – KP83 Sep 27 '19 at 16:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/200077/discussion-between-kp83-and-andre-nuechter). – KP83 Sep 27 '19 at 16:37
  • Your example looks like it works the way i had in mind, but i can't get it to work on my end. I copied the code checked it, placed the `data-color` and the `data-background` on to a first div to check, but nothing happens, also the console doesn't say that there are any problems. – KP83 Sep 28 '19 at 11:29
  • I changed the background-setting-logic to look up the computed style of the intersecting div. Could that be the issue? – Andre Nuechter Sep 28 '19 at 16:17
  • No nothing happens :-( – KP83 Sep 28 '19 at 19:08
  • Hm, that's weird: the snippet doesn't work when run, but the preview works when editing it. It also works when I copy-paste the code to a jsfiddle. Maybe you can get it to work by changing the 2nd line of the if-block back to `header.style.backgroundColor = entry.target.dataset.background || ''` – Andre Nuechter Sep 30 '19 at 17:23
  • Maybe it has to do with the callback of the script? – KP83 Oct 01 '19 at 08:18
  • It most likely was a typo :D It's working for me now. – Andre Nuechter Oct 01 '19 at 17:51
  • I also made some tweaks. The computed style is now only checked when no background is given via a custom data attribute and it's checked at most once. – Andre Nuechter Oct 01 '19 at 18:02
  • Thanks Andre, you've been a great help in understanding a bit more about IO. I've been adapted the code that user cloned showed me. It is somewhat working, but have some small errors that i would love to seen fixed. When you are online invite me for a chat i could show you. Love the help. – KP83 Oct 02 '19 at 08:06
  • I'm glad I could help! If you've got specific problems you can't fix on your own, best post another question ;) – Andre Nuechter Oct 02 '19 at 16:55
  • I’ve fixed one of them, will do if I can’t find the solution for the other. Thanks – KP83 Oct 02 '19 at 21:13