I have re-created a working simplified version of my issue: a skinny banner (which hides when user scrolls down), main navigation (that should stick to top of page when user scrolls down) and some dummy content to make scrolling possible.
I’m achieving this current implementation by adding a class sb-scrolling
to the skinny banner (display: none
) when the scroll position is less more than or equal to the main-nav
height.
However, when scrolling slowly, there is a flicker which seems to be between hiding and showing the skinny banner. Can anyone guide as to where I’ve gone wrong?
UPDATE: answers below still have "flicker" present (must scroll down/up slowly to spot)
const skinnyBanner = document.querySelector('.skinny-banner');
const mainNav = document.querySelector('.main-nav');
// Handle page scroll
let scrollpos = window.scrollY;
const navHeight = mainNav.offsetHeight;
window.addEventListener('scroll', function() {
scrollpos = window.scrollY;
if (scrollpos >= navHeight) {
mainNav.classList.add('scrolling');
skinnyBanner.classList.add('sb-scrolling');
} else {
mainNav.classList.remove('scrolling');
skinnyBanner.classList.remove('sb-scrolling');
}
});
header {
display: block;
position: sticky;
top: 0;
}
.skinny-banner {
display: flex;
align-items: center;
justify-content: center;
background-color: lightgrey;
width: 100%;
height: 40px;
}
.main-nav {
display: flex;
align-items: center;
justify-content: center;
background-color: yellow;
width: 100%;
height: 140px;
}
.skinny-nav-menu {
display: flex;
gap: 24px;
}
.sb-scrolling {
display: none !important;
}
.scrolling {
min-height: 70px !important;
}
.content-block-1 {
height: 300px;
background-color: orange;
display: flex;
align-items: center;
justify-content: center;
}
.content-block-2 {
height: 300px;
background-color: lightblue;
display: flex;
align-items: center;
justify-content: center;
}
.content-block-3 {
height: 300px;
background-color: lightcyan;
display: flex;
align-items: center;
justify-content: center;
}
<header>
<nav>
<div class="skinny-banner">SKINNY BANNER THAT IS HIDDEN WHEN SCROLLING DOWN</div>
<div class="main-nav">
MAIN NAVIGATION THAT SHOULD ALWAYS BE VISIBLE
</div>
</nav>
</header>
<div class="content-block-1">RANDOM PAGE CONTENT</div>
<div class="content-block-2">RANDOM PAGE CONTENT</div>
<div class="content-block-3">RANDOM PAGE CONTENT</div>
6
Answers
The problem was because of
it just make the
div
disappear the moment it’s add to that div, so you see the flicker effect.When you set the display property of the banner to none it disappears from the DOM flow, and therefore changes the scrollpos value (reducing it below your threshold, navHeight). That makes appear the banner again and starts over.
If you just one to hide the banner when you scroll down and you don’t want it to reappear, just set navHeight to -1 once scrollpos > navHeight.
You can refine this even further, but the main idea is that you have the whole header be
position: fixed;
and then when you’ve scrolled based a threshold (say 25px) you toggle the "show
" class on the header.That way you can control all the transitions, and animations directly in CSS.
And to allow space for the header, you can either fix the height of it (Which will change at different breakpoint) or you can update it when ever you toggle the class.
EDIT
Here is a new, different way that uses
position: fixed
for the header, so that it doesn’t impact the height of the document, and we will slide the top banner off the top of the screen when we scroll down. The main content of the document will have a 140pxmargin-top
, so that it isn’t hidden behind the header.END EDIT
How about something like this? I stuck the skinny banner to the top of the header with
position: fixed
, and gave themain-nav
some extra top padding so that it stays in the same spot. We can remove the extra padding once we’ve scrolled down and use a CSStransition
so it looks nice. The other change I made was setting your body margin to 0, just to make things easier.The "flicker" issue exists because when you apply "display: none" to the skinny banner, it triggers the scroll event on the page and the window scroll amount goes below the navheight value which then triggers the scroll event again and removes the "display: none" adding the skinny banner again and more height to the page which results in another scroll. Basically, at one point while scrolling, it gets stuck in an infinite loop of adding and removing the display tag which makes it "flicker".
The trick to fix this is considering the skinny banner’s height, place a check on the second else like in the attached code and subtract the skinny element height from the navheight before comparing the window scrolled amount and it’ll be fixed.
The only thing missing is a
transition: all 0.2s linear;
in.skinny-banner
. The rest is from @AlanOmarUPDATE
transition: height 0.2s linear;
should be enough.