如何用JavaScript制作一个互动且动态的目录

在各种平台上阅读一些技术文章时,我经常会注意到侧边栏中的目录部分有些是交互式的,有些只是链接到这些部分目录通常对读者非常有帮助,它可以让您轻松浏览文章内容

在浏览各种平台上的技术文章时,我一直注意到侧边栏中的目录部分。有些是交互式的,有些只是链接到这些部分。

目录对读者通常很有帮助。它可以让您轻松地浏览文章将涵盖的内容,并找到您感兴趣的部分。它还可以让您知道文章是否包含您正在寻找的信息,这对无障碍性来说是一个很大的胜利。

受到这些不同平台的启发,我尝试构建自己的目录功能。我希望它能动态列出所有的H2标题以及它们的书签链接。我还希望当它们在视图中滚动时,标题能够被突出显示。我很兴奋,让我们开始吧。

注意:我不能使用Codepen,因为它使用iframes来预览结果 – 就目前而言,Intersection Observer在iFrame中表现得相当反复无常。这是这段代码的gist

Intersection ObserverIntersection Observer. GitHub Gist: instantly share code, notes, and snippets.favicon262588213843476Gistgist-og-image-54fd7dc0713e

先决条件

为了充分利用本教程,您应该熟悉:

  1. HTML5 / CSS3 / JavaScript
  2. Intersection Observer API

好的,现在让我们开始吧。

设置项目

首先,让我们为我们的目录设置HTML结构。它并不复杂 – 只是一个将所有内容包裹起来的<article>标签,以及一个<aside>标签作为兄弟节点,最外层是<main>标签。

如下所示:

<main>    <article>        <h1>主标题</h1>        <h2>第一个标题</h2>        <p>Lorem ipsum dolor sit...</p>        <h2>第二个标题</h2>        <p>Lorem ipsum dolor sit...</p>        <h2>第三个标题</h2>        <p>Lorem ipsum dolor sit...</p>    </article>    <aside></aside></main>

由于<aside>标签将根据<article>中的内容进行填充,所以它是空的。

结构部分已经完成。让我们进行一些样式设置,以帮助我们区分非活动链接和活动链接。

如何添加样式

我已经导入了一个名为DM Sans的Google字体来进行这个小项目。在我的CSS中,我正在使用原生嵌套。

@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;600&display=swap');html {    scroll-padding: 3.125rem;     font-family: 'DM Sans', sans-serif;  }  main {    display: grid;    gap: 2rem;    grid-template-columns: 3fr 1fr;  }  aside {    align-self: start;    position: sticky;    top: 0.625rem;    ul {      li {        a {          transform-origin: left;          transition: transform 0.1s linear;          &.active {            font-weight: 600;            transform: scale(1.1);          }        }      }    }  }  @media (max-width: 767px) {    main {      grid-template-columns: 1fr;  }    aside {      display: none;    }  }

我使用了 display: grid; 来组合布局,使得内容占据容器(在这种情况下,是视口)的四分之三空间,而目录占据剩余的四分之一空间。

我将 <aside> 设置为粘性定位,这样它在内容滚动时仍然可见。我们需要体验一下“目录”的交互和行为,对吧?

如何构建逻辑

现在,来到了有趣的部分-也是最重要的部分。让我们先从容易实现的开始,并在此基础上构建。

构建动态目录功能

首先,我们需要将所有的 H2 元素存储在一个变量中,这是我们在第一行中要做的事情。接下来,我们将选择 aside 元素,因为我们要用一些东西填充它。然后,我们创建一个新的 ul 元素,并将其存储在变量 ul 中。之后,我们将新创建的 ul 元素附加为 aside 元素的子元素。

代码如下:

const headings = Array.from(document.getElementsByTagName("h2"));const aside = document.querySelector("aside");const ul = document.createElement("ul");aside.appendChild(ul);headings.map((heading) => {    const id = heading.innerText.toLowerCase().replaceAll(" ", "_");    heading.setAttribute("id", id);    const anchorElement = `<a href="#${id}">${heading.textContent}</a>`;    const keyPointer = `<li>${anchorElement}</li>`;    ul.insertAdjacentHTML("beforeend", keyPointer);});

现在,我们使用 map 函数遍历每个 H2 元素并执行一个函数。首先,我们通过将文本内容转换为小写并将空格替换为下划线来为每个 h2 元素创建一个 id。这个 id 用于链接到对应的部分。

接下来,我们使用刚刚创建的 id,并将其设置为 ‘id’ 属性的值。然后,我们创建一个指向生成的 idhref 属性的锚点元素(<a>)。锚点文本设置为 h2 元素的文本内容。

现在,我们可以创建一个包含之前创建的锚点元素的列表项(<li>),然后将该列表项作为 HTML 追加到无序列表 (ul) 的末尾。

使目录具有交互性

好了,我们已经完成了一半!现在,我们有一个动态的目录,它自动列出所有的 h2 元素及其书签链接。

现在,我们只剩下交互部分。当相应的部分在页面视图中时,我们希望链接被突出显示。

因此,现在 aside 元素已填充并包含锚点标记,我们将存储所有这些锚点,并将其命名为 tocAnchors

const tocAnchors = aside.querySelectorAll("a");

接下来,我们将声明一个名为 obFunc 的箭头函数,它将在交叉观察器中使用。交叉观察器基本上是浏览器提供的一个 API。它允许我们观察我们想要的元素与文档视口或选择的根元素交叉的变化。

const obFunc = (entries) => {}

现在,我们定义了一个函数 obFunc,它以数组 entries 作为参数。只要观察的元素(稍后指定)与视口交叉,函数就会执行。

entriesforEach 循环中,我们检查观察到的元素是否与视口交叉。如果条件满足,则在 headings 数组中找到交叉元素(由 entry.target 表示)的索引。

entries.forEach((entry) => {        if (entry.isIntersecting) {        	const index = headings.indexOf(entry.target);        }}

使用新的 forEach 循环,我们遍历所有锚点元素(tocAnchors),并从每个元素中移除 “active” 类以确保 active 类不会同时出现在多个元素上。

tocAnchors.forEach((tab) => {    tab.classList.remove("active");});

现在,我们将 active 类添加到正在交叉的锚点元素上。此外,我们还使用 scrollIntoView 方法将页面滚动以将活动的锚点元素带入视图内。选项 { block: "nearest" } 确保它在垂直和水平方向上滚动到最近的位置。

tocAnchors[index].classList.add("active");    tocAnchors[index].scrollIntoView({        block: "nearest"});

现在,我们定义一个名为 obOption 的对象,用作 Intersection Observer 的配置。 rootMargin 指定根元素(在本例中是视口)的边距,而 threshold 设置触发回调函数的阈值。

const obOption = {    rootMargin: "-30px 0% -77%",    threshold: 1};

这里的 rootMargin 选项非常重要。它基本上通过创建与原始视口的偏移量来设置伪视口。这成为监视区域(某种程度上)。

此选项以与边距相同的方式接受值 – 只不过当我们在这里使用负值时,它通过向屏幕中心移动来进行修正。您可以使用与我相同的值并实现理想的区域,或者您可以尝试调整值,直到获得您理想的效果。

最后,我们只需使用之前定义的函数 obFunc 作为回调函数和选项 obOption 创建一个新的 Intersection Observer 实例。然后,我们将使用 forEach 循环遍历所有的 H2 元素,并使用 .observe() 方法使它们处于观察状态。

const observer = new IntersectionObserver(obFunc, obOption);headings.forEach((hTwo) => observer.observe(hTwo));

当其中任何一个元素与视口交叉时,将执行 obFunc 回调函数。

Screen-Recording-2023-11-13-at-3.22.45-PM--online-video-cutter.com-
项目演示:滚动文本时在右侧显示 ToC

总结

现在,您拥有一个完全交互和动态的目录。希望本教程对您有所帮助。如果您能在此基础上继续建设或进一步改进此项目,请告诉我。祝您成功!


Leave a Reply

Your email address will not be published. Required fields are marked *