JavaScript 实现文章目录索引导航 (TOC) 详解
本文详解如何使用 JavaScript 实现文章目录索引(TOC)功能。核心包含两点:利用 HTML 锚点实现点击跳转,以及通过监听滚动事件动态计算当前阅读段落并高亮对应目录项。文章提供了基于 jQuery 的完整源码、在线演示及 GitHub 仓库地址,并探讨了后端生成目录以优化 SEO 的方案。
JavaScript 实现文章目录索引导航 (TOC) 详解
TOC 即 Table of Content,意为文章目录索引。
具体效果可参考 Hexo 博客中的实际演示,例如访问示例站点即可体验。
实现该功能主要包含两个核心要点:
第一,点击目录跳转至对应段落,这通过 HTML <a> 标签的锚点机制实现,其具体原理可查阅相关文档。第二,滚动时目录项自动变换高亮状态,这需要 JavaScript 监听页面的滚动事件,实时判断用户当前所处的阅读段落,并更新目录中对应项的高亮样式。为此,我编写了一个简单的 Demo 来直观展示这一效果。
完整源码已开源,地址为:https://github.com/owenliang/js-toc
您可以访问以下链接在线体验实际效果:http://owenliang.github.io/js-toc
实现逻辑分析
接下来对实现逻辑进行具体分析。
页面结构上,#toc 元素负责展示左侧目录列表,#content 元素则承载右侧的文章正文内容。
<div id="toc">
<ul>
</ul>
</div>
<div id="content">
<a name="seg-1" class="seg-begin"><h1>第1章节</h1></a>
<div class="seg-content"></div>
<a name="seg-2" class="seg-begin"><h1>第2章节</h1></a>
<div class="seg-content"></div>
<a name="seg-3" class="seg-begin"><h1>第3章节</h1></a>
<div class="seg-content"></div>
<a name="seg-4" class="seg-begin"><h1>第4章节</h1></a>
<div class="seg-content"></div>
</div>
通过 CSS 样式控制,使 #toc 固定在左侧显示,并将当前激活的目录项文字设为红色;同时设置 #content 靠左偏移以留出目录空间,并填满屏幕剩余宽度。
#toc {
width: 200px;
position: fixed;
left: 0;
top: 0;
}
#toc a.active {
color: red;
}
#content {
margin-left: 200px;
}
在上述静态页面结构中,目录区域初始为空,因为具体的目录列表需要通过 JavaScript 动态生成。
在正文内容中,需要人工预先标记各段落的起始位置,即添加 class 为 seg-begin 的 <a> 标签作为锚点。每个段落的锚点 name 属性必须唯一,且该锚点之后紧跟着对应的段落内容。
在 JavaScript 逻辑中,首先按照锚点在文档中出现的顺序,收集所有 class 为 seg-begin 的元素并保存到 segs 数组中,该数组的顺序即对应文章自上而下的阅读顺序。随后,根据这些锚点关联的 <h1> 标签文本,构建出 #toc 区域内的 <ul> 列表结构。
var segs = [];
$(".seg-begin").each(function (idx, node) {
segs.push(node)
var link = $("<a></a>").attr("href", "#" + $(node).attr("name")).html($(node).children("h1").html())
if (!idx) {
link.addClass("active")
}
var row = $("<li></li>").append(link)
$("#toc ul").append(row)
})
接着,为浏览器窗口绑定 scroll 滚动事件进行监听。每次发生滚动时,程序会判断最近一个滚出屏幕顶部的 seg-begin 节点,该节点即代表用户当前正在阅读的段落。
$(window).bind("scroll", function() {
var scrollTop = $(this).scrollTop()
var topSeg = null
for (var idx in segs) {
var seg = segs[idx]
if (seg.offsetTop > scrollTop) {
continue
}
if (!topSeg) {
topSeg = seg
} else if (seg.offsetTop >= topSeg.offsetTop) {
topSeg = seg
}
}
if (topSeg) {
$("#toc a").removeClass("active")
var link = "#" + $(topSeg).attr("name")
console.log('#toc a[href="' + link + '" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ]')
$('#toc a[href="' + link + '" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ]').addClass("active")
// console.log($(topSeg).children("h1").text())
}
})
后续优化方向
后续优化方向。
目前目录的生成完全依赖前端 JavaScript 根据正文锚点动态构建。为了提升搜索引擎优化(SEO)效果,建议在文章发布时由后端直接匹配正文中的锚点信息,并将生成的目录结构直接保存为静态 HTML,从而避免客户端渲染开销。
以下是完整的实现代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
* {
margin: 0;
padding: 0;
word-break: break-all;
}
#toc {
width: 200px;
position: fixed;
left: 0;
top: 0;
}
#toc a.active {
color: red;
}
#content {
margin-left: 200px;
}
</style>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
for (var i = 0; i < 50; ++i) {
$(".seg-content").append("<p>一个段落而已</p>")
}
(function () {
var segs = [];
$(".seg-begin").each(function (idx, node) {
segs.push(node)
var link = $("<a></a>").attr("href", "#" + $(node).attr("name")).html($(node).children("h1").html())
if (!idx) {
link.addClass("active")
}
var row = $("<li></li>").append(link)
$("#toc ul").append(row)
})
$(window).bind("scroll", function() {
var scrollTop = $(this).scrollTop()
var topSeg = null
for (var idx in segs) {
var seg = segs[idx]
if (seg.offsetTop > scrollTop) {
continue
}
if (!topSeg) {
topSeg = seg
} else if (seg.offsetTop >= topSeg.offsetTop) {
topSeg = seg
}
}
if (topSeg) {
$("#toc a").removeClass("active")
var link = "#" + $(topSeg).attr("name")
console.log('#toc a[href="' + link + '" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ]')
$('#toc a[href="' + link + '" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ]').addClass("active")
// console.log($(topSeg).children("h1").text())
}
})
})()
})
</script>
</head>
<body>
<div id="toc">
<ul>
</ul>
</div>
<div id="content">
<a name="seg-1" class="seg-begin"><h1>第1章节</h1></a>
<div class="seg-content"></div>
<a name="seg-2" class="seg-begin"><h1>第2章节</h1></a>
<div class="seg-content"></div>
<a name="seg-3" class="seg-begin"><h1>第3章节</h1></a>
<div class="seg-content"></div>
<a name="seg-4" class="seg-begin"><h1>第4章节</h1></a>
<div class="seg-content"></div>
</div>
</body>
</html>
此外,当前示例未实现嵌套层级的目录结构。经过观察 Hexo 的实现方式,发现可以通过 h1、h2、h3 等标题标签的层级关系来表达嵌套结构。在遍历生成目录时,利用这些标题标签的信息即可完成嵌套层级的标记,从而解决该问题。
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
