固定导航栏是一个流行的设计选择,因为它为用户提供了持久访问网站的入口。 另一方面,它占用页面空间,有时会覆盖内容,看起来不太美观。
可能的解决方案? 智能导航栏。
让我们定义“智能导航栏”为
- 在页面顶部可见
- 当用户向上滚动页面时可见(无论他们滚动到哪里)
- 当用户向下滚动页面时隐藏
以下是如何工作的示例
它拥有固定定位的所有便利性,并附带了全屏益处。 这种智能导航栏已经很常见(想想许多移动浏览器中的 URL 栏),但在没有库或插件的情况下实现起来有时很麻烦。 因此,在本文中,我们将讨论如何使用 CSS 和原生 JavaScript 构建一个。
旁注: 人们对向下滚动页面的定义有所不同(想象一下,某些触控板首选项在您向下移动手指时将页面向上滚动)。 就本文而言,向下滚动是指向页面底部移动。
让我们看一下代码
以下是一些示例 HTML。 我们的智能导航栏将是<nav>
,它位于<main>
之上
<nav>
<div class="logo">
Logo
</div>
<div class="links">
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#">Link 3</a>
<a href="#">Link 4</a>
</div>
</nav>
<main>
<!--Place the content of your page here-->
</main>
需要注意的是,元素仅相对于其父容器是固定的。 <nav>
的父容器应该是 body 标签; 它不应该放在页面上的其他标签内。
智能导航栏的 CSS 如下所示
nav {
position: sticky;
top: 0;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 1.5rem 2rem;
background-color: #eaeaea;
}
现在我们需要检测用户何时滚动页面以及滚动的方向。 如果用户最后滚动的位置值小于当前滚动位置的值,则用户正在向下滚动。 拆分逻辑,我们需要
- 定义一个变量来存储之前的滚动位置
- 分配一个变量来检测当前滚动位置,设置为页面的滚动偏移量
如果当前滚动位置大于之前的滚动位置,则用户正在向下滚动。 让我们将我们的函数命名为isScrollingDown
let previousScrollPosition = 0;
const isScrollingDown = () => {
let currentScrolledPosition = window.scrollY || window.pageYOffset;
let scrollingDown;
if (currentScrolledPosition > previousScrollPosition) {
scrollingDown = true;
} else {
scrollingDown = false;
}
previousScrollPosition = currentScrolledPosition;
return scrollingDown;
};
以下是此函数工作原理的视觉表示
有了这个逻辑,我们就能检测到何时页面向下滚动,因此可以使用它来切换导航栏样式
const nav = document.querySelector('nav');
const handleNavScroll = () => {
if (isScrollingDown()) {
nav.classList.add('scroll-down');
nav.classList.remove('scroll-up')
} else {
nav.classList.add('scroll-up');
nav.classList.remove('scroll-down')
}
}
如果用户正在向下滚动,我们将分配一个.scroll-down
类,其中包含页面向下移动时的样式方法。 我们可以将<nav>
CSS 更新为以下内容
nav {
/* default styling */
transition: top 500ms ease-in-out;
}
nav.scroll-up {
top: 0;
}
nav.scroll-down {
top: -100%;
}
有了这个样式,<nav>
的top
属性值被设置为页面高度的 -100%,因此它会滑出视野。 我们也可以选择使用translate
处理我们的样式,或将其淡出——任何适合的动画都可以。
性能
无论何时使用滚动事件监听器,我们都应该立即想到性能。 现在,我们在用户每次滚动页面时都会调用我们的函数,但我们不需要检测每个像素的移动。
在这种情况下,我们可以改为实现节流函数。 节流函数是一个高阶函数,充当传递给它的函数的计时器。 如果我们使用 250 毫秒的计时器对滚动事件进行节流,则该事件仅在用户滚动时每 250 毫秒调用一次。 这是一个限制调用函数次数的好方法,有助于提高页面性能。
David Corbacho 在 这篇文章中更深入地介绍了节流实现。
JavaScript 中简单的节流实现如下所示
// initialize a throttleWait variable
var throttleWait;
const throttle = (callback, time) => {
// if the variable is true, don't run the function
if (throttleWait) return;
// set the wait variable to true to pause the function
throttleWait = true;
// use setTimeout to run the function within the specified time
setTimeout(() => {
callback();
// set throttleWait to false once the timer is up to restart the throttle function
throttleWait = false;
}, time);
}
然后,我们可以将handleNavScroll
函数包含在节流中
window.addEventListener("scroll", () => {
throttle(handleNavScroll, 250)
});
有了这个实现,handleNavScroll
函数每 250 毫秒仅调用一次。
无障碍性
无论何时在 JavaScript 中实现自定义功能,我们都必须始终考虑无障碍性。 一个这样的问题是确保<nav>
在获得焦点时可见。 浏览器默认情况下往往会滚动到当前获得焦点的页面部分,但在使用滚动事件时可能会出现一些复杂情况。
确保<nav>
始终可见的一种方法是更新 CSS 以考虑焦点。 现在,我们的 CSS 如下所示
nav.scroll-up,
nav:focus-within {
top: 0;
}
不幸的是,focus-within
选择器并非在所有浏览器中都完全受支持。 我们可以包含一个 JavaScript 回退
const handleNavScroll = () => {
if (isScrollingDown() && !nav.contains(document.activeElement))) {
nav.classList.add('scroll-down');
nav.classList.remove('scroll-up')
} else {
nav.classList.add('scroll-up');
nav.classList.remove('scroll-down')
}
}
在此更新后的函数中,我们仅在用户向下滚动页面且<nav>
中当前没有任何获得焦点的元素时应用scroll-down
类。
无障碍性的另一个方面是考虑到某些用户可能不希望页面上出现任何动画。 我们可以使用prefers-reduced-motion
CSS 媒体查询来检测和尊重这一点。 我们可以使用 JavaScript 更新此方法,并在用户更喜欢减少运动时完全阻止我们的函数运行
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
window.addEventListener("scroll", () => {
if (mediaQuery && !mediaQuery.matches) {
throttle(handleNavScroll, 250)
}
});
总结
因此,我们已经拥有了: 使用纯 CSS 和原生 JavaScript 实现的智能导航栏。 现在,用户可以持久访问网站,而不会以阻止内容的方式浪费宝贵的页面空间。
此外,这种自定义实现的优势在于,我们可以获得令人愉悦的用户体验,它不会过度设计,也不会牺牲性能或无障碍性。
对于节流,我认为立即运行函数(如果冷却时间已结束)比安排它等待节流计时器更有意义——这使得操作更具响应性(操作应该在 100 毫秒内完成,据我所知)因为 250-500 毫秒的等待对于通常会立即发生的特性来说感觉很迟钝
奇怪的是,滚动函数的功能示例对我有用,但导航栏示例不起作用(Android,Chrome)。
在您将其添加到网页之前要考虑的意见: 我作为用户讨厌那些导航栏。 它们总是倾向于出现并覆盖我尝试在手机上阅读的部分。
我向下滚动到我感兴趣的部分,以便使用所有屏幕空间并停止滚动——但要到达那里,我通常必须进行一个小幅“向上”移动——砰——可见区域的顶部不再是可见区域的顶部,因为该菜单决定覆盖顶部部分。 所以,我必须再次向下滚动。 菜单消失——所以我渴望再次向上滚动以使用所有屏幕空间。 但是当我停止时: 菜单重新出现并再次覆盖我的内容。
哎哟。
谢谢! 易于理解且很有帮助!
我真的很讨厌那种导航栏。 也很不直观。 您向下滚动,阅读内容,然后想导航到另一个页面,但现在没有菜单栏了。 现在用户必须知道向上滚动一点以再次显示菜单栏。
我认为最好显示菜单栏所在位置的视觉提示。 一个不错的方法是将栏折叠成一个浮动按钮。 这样,用户始终可以按按钮再次打开导航栏。
一种解决方案是在用户停止滚动时显示导航栏。
很棒的文章。 我喜欢原生 JS 解决方案! ❤️
只是想一下,您在这里可以使用
DOMTokenList.replace()
方法吗?所以,不是这样
您将有类似以下内容
感谢分享!
有人知道如何在 iPhone/iPad 上的 Safari 中解决头部不返回的问题吗?就好像它没有意识到它已经回到了窗口顶部。
不错,让我们尝试一个新的私人项目。让我们测试一下