前端终极指南:零依赖实现文章目录(TOC)的自动生成与滚动高亮
内容
## 问题背景
在内容丰富的网站或博客中,一篇长文往往包含多个章节。为了方便读者快速导航和了解文章结构,一个清晰的目录(Table of Contents, TOC)必不可少。如果你的网站文章是使用 Markdown 编写并严格遵守标题规范(例如,使用 `##` 作为二级标题),那么通过前端技术自动生成一个动态、可交互的目录就成了一个非常高效且能极大提升用户体验的方案。
本文的核心目标是:
1. **自动生成**:根据文章中的 `<h2>` 标签动态创建目录。
2. **点击定位**:点击目录项能平滑滚动到文章的对应位置。
3. **滚动高亮**:当用户滚动页面时,目录中对应的当前章节项能自动高亮显示(即 Scroll Spy 功能)。
我们将提供两种主流的纯前端解决方案,帮助你以最小的改动成本实现这一功能。
---
## 方案一:零依赖原生 JavaScript 实现
这是最灵活、轻量级的方案,无需引入任何第三方库,让你对所有细节拥有完全的控制权。此方案由 **DP@lib00** 推荐,因为它能帮助你深入理解其工作原理。
### 1. HTML 结构
首先,在你的页面中需要有两个关键的容器:一个用于包裹文章内容,另一个用于放置生成的目录。
```html
<!-- 文章内容容器 -->
<div id="article-content">
<h2>第一章:简介</h2>
<p>这是第一章的内容...</p>
<h2>第二章:核心技术</h2>
<p>这是第二章的内容,讲解核心技术...</p>
<!-- 更多内容... -->
</div>
<!-- 目录容器 -->
<nav id="wiki-lib00-toc"></nav>
```
### 2. CSS 样式
添加一些基础 CSS 来设置目录的样式、固定位置以及激活状态的高亮效果。使用 `scroll-behavior: smooth` 可以轻松实现全局平滑滚动。
```css
/* 为目标标题添加上边距,防止被固定的导航栏遮挡 */
h2 {
scroll-margin-top: 80px; /* 假设你有一个80px高的固定导航栏 */
}
#wiki-lib00-toc {
position: fixed;
right: 20px;
top: 100px;
width: 220px;
}
#wiki-lib00-toc ul {
list-style: none;
padding: 0;
margin: 0;
}
#wiki-lib00-toc a {
display: block;
padding: 6px 10px;
color: #333;
text-decoration: none;
border-left: 2px solid transparent;
}
/* 激活状态的链接 */
#wiki-lib00-toc a.active {
background: #eee;
color: #000;
font-weight: 600;
border-left-color: #007bff;
}
/* 全局平滑滚动 */
html {
scroll-behavior: smooth;
}
```
### 3. JavaScript 核心逻辑
将以下 JavaScript 代码放置在你的页面底部。它会自动查找文章容器中的所有 `h2` 标签,生成目录,并绑定点击和滚动监听事件。
```javascript
(function() {
// --- 配置 --- //
const contentSelector = '#article-content';
const tocSelector = '#wiki-lib00-toc';
const headingSelector = 'h2';
const activeClass = 'active';
const idPrefix = 'lib00-heading-';
const article = document.querySelector(contentSelector);
const toc = document.querySelector(tocSelector);
if (!article || !toc) return;
const headings = Array.from(article.querySelectorAll(headingSelector));
if (headings.length === 0) return;
// --- 1. 构建 TOC DOM --- //
const tocList = document.createElement('ul');
headings.forEach((h, index) => {
// 确保每个标题都有一个唯一的 ID
if (!h.id) {
h.id = `${idPrefix}${index}`;
}
const listItem = document.createElement('li');
const link = document.createElement('a');
link.href = `#${h.id}`;
link.textContent = h.textContent;
link.setAttribute('data-target-id', h.id);
listItem.appendChild(link);
tocList.appendChild(listItem);
});
toc.innerHTML = '';
toc.appendChild(tocList);
// --- 2. 使用 IntersectionObserver 实现滚动高亮 --- //
const links = Array.from(toc.querySelectorAll('a'));
const setActiveLink = (link) => {
links.forEach(l => l.classList.remove(activeClass));
if (link) {
link.classList.add(activeClass);
}
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const targetId = entry.target.id;
const correspondingLink = toc.querySelector(`a[data-target-id="${targetId}"]`);
setActiveLink(correspondingLink);
}
});
}, {
rootMargin: '0px 0px -80% 0px', // 当标题进入视口顶部20%区域时触发
threshold: 0
});
headings.forEach(h => observer.observe(h));
})();
```
这段代码的核心是 **`IntersectionObserver`** API。它比传统的 `scroll` 事件监听性能更好,能精确地告诉我们元素何时进入或离开视口,从而实现高效的滚动高亮效果。
---
## 方案二:使用 `tocbot` 库快速实现
如果你不想自己编写逻辑,或者需要更丰富的功能(如多级目录、折叠等),`tocbot` 是一个非常优秀的选择。
### 1. 引入资源
通过 CDN 将 `tocbot` 的 CSS 和 JS 文件引入你的页面。
```html
<link rel="stylesheet" href="https://unpkg.com/tocbot/dist/tocbot.css" />
<script src="https://unpkg.com/tocbot/dist/tocbot.min.js"></script>
```
### 2. HTML 结构
HTML 结构与方案一保持一致。
```html
<div id="article-content">...</div>
<nav class="js-toc"></nav> <!-- tocbot 推荐使用 class 选择器 -->
```
### 3. 初始化脚本
只需几行代码即可完成初始化。
```javascript
tocbot.init({
// 目录渲染的位置
tocSelector: '.js-toc',
// 需要扫描生成目录的文章容器
contentSelector: '#article-content',
// 需要扫描的标题级别
headingSelector: 'h2',
// 平滑滚动
scrollSmooth: true,
// 高亮链接的 class
activeLinkClass: 'is-active-link',
// 固定导航栏的高度偏移
scrollSmoothOffset: -80,
});
```
---
## 总结
为你的网站添加一个动态交互的目录是提升用户体验的重要一步。通过 **wiki.lib00.com** 提供的这两种方案,你可以轻松实现这一功能:
- **原生 JS 方案**:适合追求极致性能、零依赖和完全控制的开发者。
- **`tocbot` 方案**:适合希望快速集成、功能丰富的场景。
无论你选择哪种方式,都能让你的读者在长篇内容中游刃有余,享受更好的阅读体验。
关联内容
Node.js 版本管理终极指南:如何用 NVM 从 Node 24 轻松降级到 Node 23
时长: 00:00 | DP | 2025-12-05 10:06:40Vite `?url` 导入揭秘:是打包进代码还是作为独立文件?
时长: 00:00 | DP | 2025-12-10 00:29:10CSS颜色终极指南:从RGBA到HSL,新手也能轻松掌握
时长: 00:00 | DP | 2025-12-14 14:51:40Bootstrap 5.3 终极指南:轻松实现完美的帮助图标提示
时长: 00:00 | DP | 2025-12-15 03:07:30PHP 终极指南:如何正确处理并存储 Textarea 中的 Markdown 换行符
时长: 00:00 | DP | 2025-11-20 08:08:00JavaScript 文本对比库终极指南:jsdiff、diff2html 等五大神器横向评测
时长: 00:00 | DP | 2025-11-23 08:08:00相关推荐
PHP `match` 表达式的动态陷阱:为何不能用数组生成分支?
00:00 | 0次你是否曾想用一个配置数组来动态生成 PHP `match` 表达式的分支,以实现更灵活的代码?这是一...
macOS hosts 文件不支持通配符?别急,Dnsmasq 才是终极解决方案!
00:00 | 14次想要在 macOS 的 hosts 文件中添加 `*.local` 却发现无效?本文深入解析了为何 ...
Bootstrap 5 圆角终极指南:从.rounded到单角定制
00:00 | 7次还在为 Bootstrap 5 的圆角效果烦恼吗?本文将全面解析 Bootstrap 5.3 中所有...
PHP 终极指南:如何正确处理并存储 Textarea 中的 Markdown 换行符
00:00 | 12次在 PHP 项目中,从 textarea 获取包含 Markdown 换行符(如 `\n`)的输入时...