我已经重新创建了我的问题的工作简化版本:一个瘦横幅(当用户向下滚动时隐藏)、主导航(当用户向下滚动时应该坚持到页面顶部)和一些虚拟内容以使滚动成为可能。
我通过在滚动位置小于或等于
sb-scrolling
高度时向瘦横幅(display: none
)添加类main-nav
来实现当前的实现。
但是,当缓慢滚动时,有一个闪烁,似乎在隐藏和显示瘦横幅之间。谁能指导我哪里出错了?
更新:下面的答案仍然存在“闪烁”(必须缓慢向下/向上滚动才能发现)
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>
这是一种新的、不同的方式,它使用
position: fixed
作为标题,这样它就不会影响文档的高度,当我们向下滚动时,我们会将顶部横幅从屏幕顶部滑出。文档的主要内容将有一个 140px margin-top
,这样它就不会隐藏在标题后面。
const header = document.getElementById("header");
// Handle page scroll
const navHeight = document.getElementsByClassName("main-nav")[0].offsetHeight;
window.addEventListener('scroll', () => {
const scrollpos = window.scrollY;
if (scrollpos >= navHeight) {
header.classList.add("moveUp");
} else {
header.classList.remove("moveUp");
}
});
body{
--mainNavHeight: 140px;
margin: 0px;
}
main{
margin-top: var(--mainNavHeight);
}
header {
display: block;
position: fixed;
left: 0;
right: 0;
top: 0;
transition: transform 0.4s;
}
.moveUp{
transform: translateY(-40px);
}
.centerContent{
display: flex;
align-items: center;
justify-content: center;
}
.skinny-banner {
background-color: lightgrey;
width: 100%;
height: 40px;
top: 0;
}
.main-nav {
background-color: yellow;
width: 100%;
height: var(--mainNavHeight);
}
.content-block {
height: 300px;
}
<header id="header">
<nav>
<div class="skinny-banner centerContent">SKINNY BANNER THAT IS HIDDEN WHEN SCROLLING DOWN</div>
<div class="main-nav centerContent">
MAIN NAVIGATION THAT SHOULD ALWAYS BE VISIBLE
</div>
</nav>
</header>
<main>
<div class="content-block centerContent" style="background-color: orange">RANDOM PAGE CONTENT</div>
<div class="content-block centerContent" style="background-color: lightblue">RANDOM PAGE CONTENT</div>
<div class="content-block centerContent" style="background-color: lightcyan">RANDOM PAGE CONTENT</div>
</main>
这样的事情怎么样?我用
position: fixed
将瘦横幅粘贴到标题的顶部,并给 main-nav
一些额外的顶部填充,以便它保持在同一个位置。一旦我们向下滚动并使用 CSS transition
,我们就可以删除额外的填充,所以它看起来不错。我做的另一个改变是将你的 body margin 设置为 0,只是为了让事情变得更容易。
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');
}
});
body{
margin: 0px;
}
header {
display: block;
position: sticky;
top: 0;
}
.skinny-banner {
display: flex;
align-items: center;
justify-content: center;
background-color: lightgrey;
width: 100%;
height: 40px;
position: fixed;
top: 0;
}
.main-nav {
display: flex;
align-items: center;
justify-content: center;
background-color: yellow;
width: 100%;
height: 140px;
padding-top: 40px;
transition: padding .5s;
}
.skinny-nav-menu {
display: flex;
gap: 24px;
}
.sb-scrolling {
display: none;
}
.scrolling {
min-height: 70px !important;
padding-top: 0px;
}
.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>
问题是因为
.sb-scrolling {
display: none !important;
}
它只是让
div
在它添加到那个div的那一刻消失,所以你看到闪烁效果。
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 {
height: 0 !important;
padding: 0;
transition: all 0.2s linear;
overflow:hidden;
}
.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>
当您将横幅的显示属性设置为 none 时,它会从 DOM 流中消失,因此会更改 scrollpos 值(将其降低到您的阈值 navHeight 以下)。这使得横幅再次出现并重新开始。
如果您只是想在向下滚动时隐藏横幅并且不希望它重新出现,只需将 navHeight 设置为 -1 一次 scrollpos > navHeight。
您可以进一步完善它,但主要思想是让整个标题为
position: fixed;
然后当您基于阈值(比如 25px)滚动时,您切换标题上的“show
”类。
这样你就可以直接在 CSS 中控制所有的过渡和动画。
为了给标题留出空间,您可以固定它的高度(它会在不同的断点处改变),或者您可以在切换类时更新它。
let headerHeight = '85px';
let showingSkinny = true;
const updateCSSHeightVar = (height) => {
document.body.style.setProperty('--header-height', height + 'px');
};
const showSkinny = (show, header) => {
if (show === showingSkinny) return;
showingSkinny = show;
if (show) {
header.classList.add('show');
} else {
header.classList.remove('show');
}
headerHeight = header.clientHeight;
updateCSSHeightVar(headerHeight);
};
onload = (event) => {
console.log('loaded');
const header = document.querySelector('#header');
headerHeight = header.clientHeight;
updateCSSHeightVar(headerHeight);
addEventListener("scroll", (event) => {
if (window.scrollY > 25) showSkinny(true, header)
else showSkinny(false, header);
});
};
header {
position: fixed;
top: 0;
z-index: 1;
background: white;
width: 100%;
transition: 500ms;
}
header.show .skinny {
max-height: 0;
opacity: 0;
}
.skinny {
transition: 300ms;
}
.main-nav {
background: yellow;
}
main {
margin-top: var(--header-height);
}
.block {
height: 500px;
background: blue;
}
.block:nth-child(even) {
background: red;
}
<body>
<header id="header">
<div class="skinny"><p>Skinny</p></div>
<div class="main-nav"><p>Main nav</p></div>
</header>
<main>
<div class="block"><p>Hello World!</p></div>
<div class="block"><p>Hello World!</p></div>
<div class="block"><p>Hello World!</p></div>
<div class="block"><p>Hello World!</p></div>
<div class="block"><p>Hello World!</p></div>
</main>
</body>
“闪烁”问题的存在是因为当您将“显示:无”应用于瘦横幅时,它会触发页面上的滚动事件并且窗口滚动量低于导航高度值,然后再次触发滚动事件并删除“显示:无”再次向页面添加瘦横幅和更高的高度,从而导致另一个滚动。基本上,在滚动时的某个时刻,它陷入了添加和删除显示标签的无限循环,这使得它“闪烁”。
解决这个问题的技巧是考虑瘦横幅的高度,在附加代码中检查第二个 else 并在比较窗口滚动量之前从 navheight 中减去瘦元素高度,它将被修复。
const skinnyBanner = document.querySelector('.skinny-banner');
const mainNav = document.querySelector('.main-nav');
// Handle page scroll
let scrollpos = window.scrollY;
const navHeight = mainNav.offsetHeight;
let bannerHeight = 0;
let skipScroll = false;
window.addEventListener('scroll', function() {
scrollpos = window.scrollY;
if (scrollpos >= navHeight && !skipScroll) {
bannerHeight = skinnyBanner.offsetHeight;
mainNav.classList.add('scrolling');
skinnyBanner.classList.add('sb-scrolling');
skipScroll = true;
} else if (scrollpos < navHeight - bannerHeight) {
bannerHeight = 0;
if(skipScroll){
skipScroll = false;
}
else{
mainNav.classList.remove('scrolling');
skinnyBanner.classList.remove('sb-scrolling');
}
}
});
唯一缺少的是
transition: all 0.2s linear;
中的.skinny-banner
。其余来自@AlanOmar
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) {
skinnyBanner.classList.add('sb-scrolling');
} else {
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;
transition: all 0.2s linear;
}
.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 {
height: 0 !important;
padding: 0;
transition: all 0.2s linear;
overflow:hidden;
}
.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>
更新
transition: height 0.2s linear;
应该足够了。