使用原生 JavaScript 创建智能导航栏

Avatar of Jemima Abu
Jemima Abu

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

固定导航栏是一个流行的设计选择,因为它为用户提供了持久访问网站的入口。 另一方面,它占用页面空间,有时会覆盖内容,看起来不太美观。

可能的解决方案? 智能导航栏。

让我们定义“智能导航栏”为

  1. 在页面顶部可见
  2. 当用户向上滚动页面时可见(无论他们滚动到哪里)
  3. 当用户向下滚动页面时隐藏

以下是如何工作的示例

它拥有固定定位的所有便利性,并附带了全屏益处。 这种智能导航栏已经很常见(想想许多移动浏览器中的 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;
}

现在我们需要检测用户何时滚动页面以及滚动的方向。 如果用户最后滚动的位置值小于当前滚动位置的值,则用户正在向下滚动。 拆分逻辑,我们需要

  1. 定义一个变量来存储之前的滚动位置
  2. 分配一个变量来检测当前滚动位置,设置为页面的滚动偏移量

如果当前滚动位置大于之前的滚动位置,则用户正在向下滚动。 让我们将我们的函数命名为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 实现的智能导航栏。 现在,用户可以持久访问网站,而不会以阻止内容的方式浪费宝贵的页面空间。

此外,这种自定义实现的优势在于,我们可以获得令人愉悦的用户体验,它不会过度设计,也不会牺牲性能或无障碍性。