当你为freeCodeCamp、Hashnode、Medium或DEV.to创建文章时,可以通过添加目录来帮助读者更好地理解文章内容。在这篇文章中,我将介绍如何利用JavaScript和浏览器的开发者工具来制作目录。虽然本文以Google Chrome开发者工具为例进行讲解,但同样的方法也适用于其他现代浏览器。

本文中的步骤需要针对每个平台分别操作一次。一旦你掌握了相应的代码,以后每次创建目录时都可以使用这些代码。需要注意的是,如果某个平台的设置发生了变化,可能就需要调整脚本了。

目录

浏览器开发者工具

浏览器开发者工具是浏览器的一个扩展功能,它允许你查看和操作DOM结构(文档对象模型),DOM是浏览器将HTML内容以树形结构存储在内存中的形式。此外,该工具还提供了JavaScript控制台,你可以在其中编写简短的代码片段来进行测试。虽然它还有很多其他功能,但在这里我们只使用这两项。

要打开Google Chrome中的开发者工具,可以按F12键,或者右键点击页面然后选择“检查”。

在Safari浏览器中,开发者工具默认是关闭的。如果想要启用它,请阅读:在Mac版Safari的“开发”菜单中使用开发者工具

一个浏览器窗口被分成两部分。右侧显示的是带有freeCodeCamp文章的笔记本电脑图片,左侧则是包含DOM树和CSS面板的浏览器开发者工具界面。

上图是DevTools的截图,其中展示了本文的内容预览。在右侧,你可以看到被选中的H1 HTML标签(即页面标题)以及应用于该标签的CSS样式。你所看到的这种树状结构其实就是DOM。

💡
在为freeCodeCamp创建目录结构时,建议在新标签页中打开预览内容。

JavaScript控制台

我们需要能够使用JavaScript控制台。在Google Chrome浏览器中,可以通过按F12键、右键点击页面然后选择“检查”选项,或者使用快捷键CTRL+SHIFT+C(Windows/Linux系统)/CMD+OPTION+C(Mac系统)来打开控制台。

在Chrome DevTools中,你可以在工具栏顶部选择“控制台”标签页,但这样会隐藏DOM树结构。更好的方法是打开底部的抽屉式界面:只需点击右上角的三个点,然后选择“显示控制台抽屉”即可。

菜单截图,用于将DevTools固定到屏幕的右侧、左侧、底部或以独立窗口的形式显示。

DevTools的工具界面如下所示:

浏览器DevTools的截图,其中显示了DOM树结构、CSS面板以及控制台抽屉。

💡
你可以忽略控制台中的任何错误或警告信息。点击抽屉左侧的🚫图标,就可以清除控制台内的内容。

控制台实际上是一种所谓的“读-执行-打印循环”界面。在这种经典的设计中,你可以输入一些命令(通常是JavaScript代码),按下回车键后,这些代码就会在DevTools所连接的页面环境中被执行。

截图显示了浏览器弹出的警告窗口以及DevTools控制台中的JavaScript代码,这些代码用于触发警告窗口的显示。

上图中,你可以看到通过控制台执行的页面警告功能。

理解DOM结构

创建目录结构的第一个步骤就是检查DOM结构并找到标题元素。这些标题元素通常表现为H1…H6标签。其中,H1标签往往代表页面的标题;在理想情况下,确实应该如此。

在我的示例中,标题元素的格式如下:

<h2 id="heading-dev-tools">Dev Tools<>/h2>

这篇文章目前只使用了H2标签来表示标题,但后续内容中我会解释如何创建嵌套的目录结构。

💡
你的标题元素必须包含“id”属性。这个属性的具体形式可以有所不同,也可以位于不同的DOM元素上,但关键是要确保它存在于DOM结构中。在文章后面部分,我会介绍几种不同的结构类型以及如何处理它们。

现在,借助DevTools,我们可以编写代码来查找所有的标题元素:

document.querySelectorAll('h2[id], h3[id], main h4[id]);

以我在freeCodeCamp上写的文章为例,这段代码会返回如下结果:

NodeList(5) [h2#heading-dev-tools, h2#heading-javascript-console, h2#heading-understanding-the-dom-structure, h2#trending-guides.col-header, h2#mobile-app.col-header]

首先,这个结果是一个NodeList对象,我们需要将其转换为数组。其次,除了我们原本想要获取的标题元素外,这个数组中还包含了网站结构中的一些其他标题元素,并非文章的主要内容。因此,我们需要找出那些真正属于我们所需标题元素的父元素。

你可以在包含这篇文章内容的空白页面上右键点击,然后选择“检查元素”。在我们的例子中,系统找到了

这个元素。因此,我们可以将选择器改写为:

document.querySelectorAll('main h2[id], main h3[id], main h4[id]);

这样,它就会只返回我们需要的标题元素了。

💡
实际上,在这里并不需要使用[id]这个属性选择器。至少在freeCodeCamp平台上是不需要的。

如何用Markdown格式创建目录

很多博客平台都支持Markdown格式,因此我们首先会学习如何使用它来创建目录。

首先,我们需要将得到的NodeList对象转换为数组。我们可以使用展开运算符来实现这一点:

[...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

接着,我们可以遍历这个数组,为每个标题元素生成对应的Markdown链接。

const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

headers.map(function(node) {
// H2标题元素的缩进应为0
const level = parseInt(node.nodeName.replace('H', '')) - 2;
const hash = node.getAttribute('id');
const indent = ' '.repeat(level * 2);
return `\({indent}* [\){node.innerText}](#${hash})`;
});

最终生成的代码如下:

(4) ['* [Dev Tools](#heading-dev-tools)', '* [JavaScript Console](#heading-javascript-console)', '* [Understanding the DOM Structure](#heading-understanding-the-dom-structure)', '* [What to do if I don’t have headers?](#heading-what-to-do-if-i-dont-have-headers)']

如果想要获取这些链接对应的文本,我们可以将数组中的所有元素用换行符连接起来,然后使用console.log来显示结果。如果不使用换行符,输出的结果会包含多个连续的换行字符。

const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

console.log(headers.map(function(node) {
// H2标题元素的缩进应为0
const level = parseInt(node.nodeName.replace('H', '')) - 2;
const hash = node.getAttribute('id');
const indent = ' '.repeat(level * 2);
return `\({indent}* [\){node.innerText}](#${hash})`;
}).join('\n'));

这篇文章的输出结果将会是这样的:

* [开发工具](#heading-dev-tools)
* [JavaScript控制台](#heading-javascript-console)
* [理解DOM结构](#heading-understanding-the-dom-structure)
* [用Markdown创建目录结构](#heading-creating-toc-in-markdown)
  * *这是一个虚拟的标题栏*

我添加了一个虚拟的子标题。虽然某些平台在编写文章时不支持Markdown格式,但当用户将Markdown内容复制粘贴后,这些平台通常能够正常显示这些内容。文章开头的目录结构就是通过复制并粘贴上面那段JavaScript代码生成的Markdown内容来创建的。

如何创建HTML目录结构

如果你的平台不支持Markdown格式(比如Medium),你可以先生成HTML代码,预览后将其复制到剪贴板中,然后粘贴到你正在使用的平台的编辑器中,这样格式应该会保持不变。

💡
在Medium平台上,内容是包含在

标签内的,因此选择器也需要相应地进行调整。

要将Markdown转换为HTML,你可以使用任何在线工具,但下面这段代码会教你如何自己编写这样的转换脚本。一旦你掌握了这个方法,以后操作起来就会更快了。

const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')]

function indent(state) {
    return ' '.repeat((state.level - 1) * 2);
}

function closeUlTags(state, targetLevel) {
    while (state.level > targetLevel) {
        state.level--;
        state_lines.push(`${indent(state)}
    `); } } function openUlTags(state, targetLevel) { while (state.level < targetLevel) { state_lines.push(`${indent(state)}
      { const level = parseInt(node.nodeName.replace('H', '')); closeUlTags(state, level); openUlTags(state, level); const hash = node.getAttribute('id'); state_lines.push `\({indent(state)}
    • ${node.innerText}
    • `); return state; }, { lines: [], level: 1 }); closeUlTags(result, 1); console.log(result(lines.join('\n'));

    这就是这篇文章中提到的代码所生成的HTML输出结果:

    我在文末添加了一些标题,这样你们就可以看到,这种方法适用于任何层级的嵌套标题。需要注意的是,列表中的第一个元素也是目录。

    💡
    请注意,上述HTML代码中包含了一个指向目录的链接。这是因为在添加了目录之后,如果再次运行脚本,就会生成这个链接。你可以手动将其删除。如果你想优化代码,还可以添加一些过滤机制。

    将生成的HTML代码复制到编辑器中

    大多数所谓的WYSIWYG编辑器都是使用HTML格式的,因此你应该可以将带有格式设置的HTML代码复制并粘贴到这些编辑器中。最简单的方法就是将代码保存为文件,然后打开该文件并选中所需的文本。

    浏览器窗口中的截图,文件已被打开。页面上显示了目录,所有被选中的文本都会高亮显示。

    如果我没有标题该怎么做?

    你需要找到那些可以通过CSS进行操作的元素。如果这些元素是带有特定类的p标签(比如“header”类),那么你可以使用p.header代替h2

    如何为DEV.to创建目录

    如果你使用的DOM结构不同,也可以使用不同的DOM方法来提取所需的元素。例如,在DEV.to上,标题的格式是这样的:

    <h2>
      <a name="overview" href="#overview">
      </a>
      Overview
    </h2>
    

    因此,选择器应该写作main h2。但是当你执行这段代码时:

    [...document.querySelectorAll('main h2, main h3, main h4')];
    

    你会发现,页面上的标题数量远远超过了正文内容。幸运的是,CSS中提供了一个新的选择器:has()。因此,针对一个特定标题的选择器可以写成main h2:has(a[name])

    以下是完整的代码:

    const selector = 'main h2:has(a[name]), main h3:has(a[name]), main h4:has(a[name])';
    const headers = [...document.querySelectorAll(selector)];
    
    console.log(headers.map(function(node) {
        // H2标题的缩进应为0
        const level = parseInt(node.nodeName.replace('H', '')) - 2;
        // 这种方法可以获取链接地址
        // 你也可以直接访问标签的href属性,并从结果字符串中删除#符号
        const hash = node.querySelector('a').getAttribute('name');
        const indent = ' '.repeat(level);
        return `\({indent}* [\){node.innerText}](#${hash})`;
    }).join('\n'));
    

    总结

    创建目录可以帮助读者更好地理解文章内容。由于大多数人并不会通读整篇文章,他们只会浏览自己感兴趣的部分。此外,也有很多研究指出目录对SEO效果有积极影响。因此,对于篇幅较长的文章来说,添加目录绝对是值得的。

    如您所见,只要具备一些网页开发方面的知识,创建目录结构其实并不困难。

    如果您喜欢这篇文章,不妨在社交媒体上关注我:(Twitter/XGitHub和/或LinkedIn)。您也可以访问我的个人网站,以及我的新博客

    Comments are closed.