Changing color of navigation bar after 100vh


I currently have a sticky navigation bar on my page. After the user scrolls down the page (Specifically at the 100vh mark), I would like the background color of the navigation bar to change. My current code changes the color after x units, however, I want it to change after the full height of the page has been scrolled (Which is, of course, different for each load).

      text-align: center;
      margin: auto auto;
      position: -webkit-sticky;
      position: fixed; 
      top: 0px;
      width: 100%;
      <nav id="navbar">
<!-- Page content -->
    window.onscroll = function(ev) {
        if (window.scrollY > 500) {
             document.getElementById("navbar").style.backgroundColor = "orange"; 
            document.getElementById("navbar").style.backgroundColor = "red"; 

Also, I would like this done without using JS libraries (so plain VanillaJS).

Thanks in advance!

Try . . . .
if(window.scrollY > window.innerHeight)

1 Like

Using a class name

It’s not a good idea to use JavaScript to set style properties.

Instead of doing that, it’s better to set a classname instead, so that CSS can then properly do its job based on that classname.

#navbar {
    background-color: red;
#navbar.scrollToBottom {
    background-color: orange;
window.onscroll = function(ev) {
	if (window.scrollY > 500) {

An example page can be found at

If the class change doesn’t trigger for you, drag the result window shorter then try again.

Using a toggle

The if/else can then be removed, because toggle can be used instead.

window.onscroll = function(ev) {
	const navbar = document.getElementById("navbar");
	navbar.classList.toggle("scrollToBottom", window.scrollY > 500);

That example is found at the updated page:

Finding page bottom

And after that, 100vh means, I think, that they have scrolled all the way to the bottom, for which we have a handy function that does that check.

function hasScrolledToPageBottom() {
    return (window.innerHeight + window.scrollY) >= document.body.offsetHeight;

We can then use that in the code to end up with:

window.onscroll = function(ev) {
	const navbar = document.getElementById("navbar");
	navbar.classList.toggle("scrollToBottom", hasScrolledToPageBottom());

And that example is found at

1 Like

There’s no point in using position:sticky if you then over-ride it with position:fixed. position:fixed should be the fallback and should come first and then the webkit prefix for sticky and finally by the real position:sticky.


#navbar {
  position: fixed; /* fallback must be first */
  position: -webkit-sticky; /* Older ios needs this*/
  position: sticky; /* this is the real one and must be last in source*/

The benefit of position:sticky is that you won’t get an overlap of content when the page hasn’t scrolled. With position:fixed the initial content will be obscured under the navbar.

If you don’t need IE11 support then you could use the Intersection Observer instead of the scroll event and avoid some bottlenecks.

I’m sure the code could be tidied up but is just proof of concept :slight_smile:

Paul in css vh means viewport height which is not the same as the body height or content height so there could be some confusion in what the OP meant. In my example the header changes when the initial 100% of the viewport has scrolled away while in your demo you only change the nav color once all the content has scrolled to the end.

Your example is probably what was wanted but I just wanted to point out the difference :slight_smile:


That works really well.

I didn’t think that I had much to add until I started poking around and did the following:

  • replace if/else with classList.toggle
  • inline the options
  • extract d.querySelector(“.myObserver”) to be screenBottom, for continuity
  • renamed calculateVisibleDiv to be updateNav
  • have the callback properly handle multiple entries, even though there’s just one entry

I still want to hide away the observer details in some other function though, so I end up with the following code:

  const screenBottom = addObserver("myObserver");
  observeIntersection(screenBottom, function updateNav(entry) {
    const navbar = d.querySelector("#navbar");
    navbar.classList.toggle("change-color", entry.isIntersecting);

Is that better? The process of working through the code has certainly helped me to better understand the observer code that’s being used.

I have been going back and forth on details such as, should the observeIntersection function apply the callback to each entry, or should the calback handle multiple entries, or just ignore them? That seems to be a toss-up either way.

Anyway, I hope that some of the update is found to be useful.


Thanks for tidying my code up Paul. I love the way you break things down step by step. :slight_smile:

Love it, works perfectly!

Out of curiosity, why?

Oh sorry, I did mean 100% of the initial viewport height, or CSS 100vh.

That’s because dealing with styles is the domain of CSS. Even though JavaScript is capable of doing so, you end up in fights and conflicts over what is being controlled by which.

For all but the most extreme of situations, greater reliability is gained by having all of the style declarations done as style sheets. That way JavaScript’s only role when it comes to styling is to change class names to control things instead.

That all comes down to the trinity of web development too, which is: HTML for content, CSS for presentation, and JS for behaviour.


Note that if the screen is resized then the method fails as the 100% isn’t updated until you scroll again. You would need to call your routine on screen resize as well as scroll if this is an issue for you (It may not be an issue unless you have a full screen that you want to scroll away before changing color). Of course you should also ‘de-bounce’ the resize and scroll events if your page is going to have a lot going on otherwise it could become a little bit clunky in use… (Note neither of these are issues on the demo I posted although there is no support for IE).

Don’t forget the css changes I mentioned as they are important also :slight_smile:

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.