使用JS IntersectionObserver让标题和导航联动

525 阅读3分钟

IntersectionObserver 是 JavaScript 中的一种 API,用于监测元素是否进入或离开视口(viewport)。使用 IntersectionObserver 可以实现许多与滚动相关的交互效果,其中之一就是让标题和导航联动。

IntersectionObserver 的基础知识

IntersectionObserver 是 Web API 中的一种,它允许我们异步监测一个目标元素与其祖先元素或顶级文档视窗(viewport)发生交集的情况。当一个目标元素进入或者离开视窗时,IntersectionObserver 会触发一个回调函数,从而可以执行相应的操作。

使用 IntersectionObserver 可以避免使用 scroll 事件等方式监测元素的可见性,这种方式可能会影响网页的性能和用户体验。

如何使用 IntersectionObserver 监测元素的可见性

下面是一个简单的示例代码,展示了如何使用 IntersectionObserver 监测一个元素是否进入视窗:

// 获取需要监测的元素
const target = document.querySelector('#target');

// 创建一个 IntersectionObserver 实例
const observer = new IntersectionObserver((entries) => {
  // entries 是一个数组,包含了所有正在被监测的元素的状态信息

  // 遍历 entries 数组,查找目标元素的状态
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      // 目标元素已经进入视窗
    } else {
      // 目标元素已经离开视窗
    }
  });
});

// 开始监测目标元素
observer.observe(target);

在上面的代码中,我们首先获取了需要监测的元素 #target,然后创建了一个 IntersectionObserver 实例,并传入了一个回调函数。回调函数会在目标元素进入或离开视窗时触发,参数 entries 是一个数组,包含了所有正在被监测的元素的状态信息。

我们可以遍历 entries 数组,查找目标元素的状态。如果目标元素的属性 isIntersectingtrue,则表示目标元素已经进入视窗;否则,表示目标元素已经离开视窗。

如何使用 JavaScript 更新导航的样式

现在我们已经知道如何使用 IntersectionObserver 监测一个元素是否进入视窗,接下来我们将介绍如何使用 JavaScript 更新导航的样式。

假设我们有一个页面,包含了多个章节标题和一个固定在页面顶部的导航栏。我们希望在用户滚动页面时,自动更新导航栏中对应章节标题的样式。

下面是一个示例代码,展示了如何使用 IntersectionObserver 和 JavaScript 实现这个功能:

<!DOCTYPE html>
<html>
  <head>
    <style>
      nav {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 50px;
        background-color: #eee;
      }
      .section {
        padding-top: 50px;
      }
      h2 {
        margin-top: -50px;
      }
      .active {
        color: red;
      }
    </style>
  </head>
  <body>
    <nav id="nav">
      <ul>
        <li><a href="#section1">Section 1</a></li>
        <li><a href="#section2">Section 2</a></li>
        <li><a href="#section3">Section 3</a></li>

我们在页面顶部添加了一个固定的导航栏,其中包含了多个章节标题的链接。每当用户滚动页面时,我们会自动更新导航栏中对应章节标题的样式,以反映当前所处的章节。

下面是完整的 HTML 和 JavaScript 代码:

```html
<!DOCTYPE html>
<html>
  <head>
    <style>
      nav {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 50px;
        background-color: #eee;
      }
      .section {
        padding-top: 50px;
      }
      h2 {
        margin-top: -50px;
      }
      .active {
        color: red;
      }
    </style>
  </head>
  <body>
    <nav id="nav">
      <ul>
        <li><a href="#section1">Section 1</a></li>
        <li><a href="#section2">Section 2</a></li>
        <li><a href="#section3">Section 3</a></li>
      </ul>
    </nav>

    <main>
      <section class="section" id="section1">
        <h2>Section 1</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed cursus vestibulum neque, ac aliquam lorem egestas rhoncus. Maecenas posuere libero quis maximus dictum.</p>
      </section>
      <section class="section" id="section2">
        <h2>Section 2</h2>
        <p>Fusce tristique, mi sed fermentum porttitor, enim ligula auctor purus, et blandit nisl risus vel ante. Nunc eget diam ut mi commodo condimentum sit amet quis diam.</p>
      </section>
      <section class="section" id="section3">
        <h2>Section 3</h2>
        <p>Cras tempus augue non elit faucibus rutrum. Nullam malesuada bibendum dolor at varius. Quisque semper, dui vel consequat aliquam, tortor lorem facilisis velit, vel vulputate odio magna vel est.</p>
      </section>
    </main>

    <script>
      // 获取导航栏和章节标题元素
      const navLinks = document.querySelectorAll('#nav a');
      const sections = document.querySelectorAll('.section');

      // 创建 IntersectionObserver 实例,监测所有章节标题元素
      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          // 如果目标元素已经进入视窗,则在导航栏中添加 active 类名
          if (entry.isIntersecting) {
            const targetId = entry.target.getAttribute('id');
            const targetLink = document.querySelector(`#nav a[href="#${targetId}"]`);
            navLinks.forEach((link) => link.classList.remove('active'));
            targetLink.classList.add('active');
          }
        });
      }, { threshold: 0.5 });

      // 开始监测所有章节标题元素
      sections.forEach((section) => observer.observe(section));
    </script>
  </body>
</html>

在上面的代码中,我们首先获取了导航栏中所有的链接元素 navLinks,以及页面中所有的章节标题元素 sections。然后,我们创建了一个 IntersectionObserver 实例,并传入了一个回调函数。

回调函数会在每个章节标题元素进入或离开视窗时触发。如果目标元素已经进入视窗,则在导航栏中找到相应的链接元素,并为其添加 active 类名;否则,从该链接元素中移除 active 类名。这样可以使用户在滚动页面时,始终知道自己所处的章节。

需要注意的是,我们在创建 IntersectionObserver 实例时还指定了一个参数 { threshold: 0.5 },用于设置交叉比例阈值。默认情况下,IntersectionObserver 认为目标元素至少有一半进入视窗才算作交叉。如果需要更改阈值,则可以通过修改该参数实现。