前几天在 GitHub 上发现一个用 JavaScript 生成文章目录的项目 Tocbot,于是顺手加到博客上了。
What's TOC?
A table of contents, usually headed simply "Contents" and abbreviated informally as TOC, is a list, usually found on a page before the start of a written work, of its chapter or section titles or brief descriptions with their commencing page numbers.A table of contents, usually headed simply "Contents" and abbreviated informally as TOC, is a list, usually found on a page before the start of a written work, of its chapter or section titles or brief descriptions with their commencing page numbers.
form Wikipedia
Use TOC in HTML
一份标准的 HTML 文档,应该有严格的格式,比如通过 <h1>
、<h2>
、<h3>
这样的标签作为大标题、小标题;用 <p>
来标记段落。(很惭愧早期的文章我自己也没有太在意标题部分,包括主题上的标题也是用得比较随性,据说这样对搜索引擎极不友好)
当然用 HTML 标签直接写文章的人应该没有几个吧,通常我们使用 Markdown 写文章,然后通过一个 parser 转换为 HTML,而 Markdown 中的 # h1 balabala
、## h2 balabala
等正对应了 HTML 中的 <h1>
、<h2>
等。所以只要你的文章是用 Markdwon 写的,生成一个目录是很容易的。
下面举几个例子,都可以在 WordPress 的原生 Markdown parser 上实现(个人并不喜欢 Jetpack 插件并且也未测试过其是否支持):
The Site {#header}
=======
在标题中指定 id
## The Site 1 {.main}
在标题中指定 class
### The Site 2 {style=color:red;}
在标题中指定 style
以上生成的标签是这样的:
<h1 id="header">The Site</h1>
<h2 class="main">The Site 1</h2>
<h3 style="color:red;">The Site 2</h3>
现在要说的 Tocbot 就是生成这些段落标记目录的。如果单纯想要一个目录其实不是难事,用 jQuery 写起来很简单的,基本思路就是遍历正文中的所有 <h>
标签,并按 <h1>
、<h2>
、<h3>
的顺序建立套嵌逻辑,最后根据 element 的 id 建立链接。当然 Tocbot 的功能比这个高级得多,具体可以看本页上的效果(未适配手机,请用电脑查看,并保证屏幕宽度在 1200px 以上),值得一提的是 Tocbot 并没有使用 jQuery,完全靠原生 JS 实现。
Make It Work
那么现在开始使用 TOC,首先为了使目录中的链接可以跳到文章的指定位置,我们通过 <a>
标签的 href
属性跳转到唯一 id
标记的元素,如 <a href="#header">
将跳转到 <h1 id="header">The Site</h1>
的位置。不过注意通过 id
跳转不支持中文 id,今可使用数字、英文、英文符号。
Unique ID
那么工作的第一步就是为每一个标题生成唯一的 id,你可以像上面那样在 Markdown 中指定 id,当然这很麻烦,那就让 JS 来帮忙吧。以下使用 jQuery 为每一个标题添加 id:
var id = 1;
$(".entry-content").children("h1,h2,h3,h4,h5").each(function () {
// entry-content 为正文容器的 class,根据自己的情况修改
//var hyphenated = $(this).text().replace(/\s/g, '-');
// 如果你希望使用中文 id 的话就用上面这行,注意非ANSI编码文字会导致无法跳转
var hyphenated = "mashiro-" + id;
$(this).attr('id', hyphenated);
id++;
});
Initial Tocbot
这时每一个标题将加上唯一的序号作为 id,下面是引入 Tocbot,并在页面中添加一个目录容器:
<link href="https://cdn.bootcss.com/tocbot/4.1.1/tocbot.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/tocbot/4.1.1/tocbot.min.js"></script>
<div class="toc"></div>
初始化 Tocbot:
tocbot.init({
// Where to render the table of contents.
tocSelector: '.toc', // 放置目录的容器
// Where to grab the headings to build the table of contents.
contentSelector: '.entry-content', // 正文内容所在
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h1, h2, h3, h4, h5', // 需要索引的标题级别
positionFixedSelector: ".toc", //目录位置固定
scrollEndCallback: function (e) { //回调函数
window.scrollTo(window.scrollX, window.scrollY - 80);
//修正滚动后页面的位置,80 是自己顶部栏的高度
},
});
还有很多可选的属性。
Customize
这时目录已经生成了,之后是随页面滚动及悬浮效果的实现。玄学 CSS 我实在写不出来,所以就用 jQuery 写了一个滚动及悬浮效果(你大概也注意到滚动的时候有一些延时,我尝试过用正弦函数修正,但延时反因运算量的增大变得更加严重,我的 JS 性能显然是比不上 CSS 的2333):
$(document).ready(function() {
if ($("div").hasClass("toc")) { // 检测是否存在目录容器
var $elm = $('.toc'); // 选择容器
var iniTop = 500; // 初始高度
var finTop = 100; // 悬浮高度
var hasScrolled = $('.site-header').offset().top; //获取当前页面距离顶部高度
//以上是根据我的header确定的,根据自己的顶部栏情况修改
if (hasScrolled > iniTop) { // 初始定位(主要针对着陆位置不在页面顶部的情况)
$elm.css({
'top': finTop
});
}
$(window).scroll(function() { // 追踪鼠标滚动事件
var p = $(window).scrollTop(); // 获取已滚动高度
if (p > iniTop - finTop) { // 悬浮定位
$elm.css({
'top': finTop
});
} else { //滚动定位
$elm.css({
'top': iniTop - p
});
}
});
}
});
以上代码控制了纵向位置,水平位置则通过下面 CSS 确定:
.toc {
width: 200px;
height: auto;
z-index: 98;
background-color: rgba(255,255,255,0);
transform: translateX(0);
right: calc((100% - 950px - 250px) / 2);
position: fixed !important;
top:480px;
position: absolute;
padding-top: 10px;
padding-bottom: 10px;
}
下面把上面 JS 整合到一个函数里:
function mashiroToc(mashiro) {
// 滚动及悬浮
$(document).ready(function() {
if ($("div").hasClass("toc")) {
var $elm = $('.toc');
var iniTop = 500;
var finTop = 100;
var hasScrolled = $('.site-header').offset().top;
if (hasScrolled > iniTop) {
$elm.css({
'top': finTop
});
}
$(window).scroll(function() {
var p = $(window).scrollTop();
if (p > iniTop - finTop) {
$elm.css({
'top': finTop
});
} else {
$elm.css({
'top': iniTop - p
});
}
});
}
});
// 初始化
if (mashiro) {
var id = 1;
$(".entry-content").children("h1,h2,h3,h4,h5").each(function() {
//var hyphenated = $(this).text().replace(/\s/g, '-');
var hyphenated = "mashiro-" + id;
$(this).attr('id', hyphenated);
id++;
});
// 初始化 tocbot.js
tocbot.init({
tocSelector: '.toc',
contentSelector: '.entry-content',
headingSelector: 'h1, h2, h3, h4, h5',
positionFixedSelector: ".toc",
scrollEndCallback: function (e) {
window.scrollTo(window.scrollX, window.scrollY - 80);
},
});
}
}
mashiroToc(true);
Examples
下面是更多层级目录的展示
This is a h3
This is a h4
This is a h5
「樱花庄的白猫」原创文章:《为文章添加一个目录》,转载请保留出处!https://2heng.xin/2018/02/13/add-a-toc-for-your-article/
Q.E.D.
Comments | 15 条评论
博主 Icey Bear
按大佬的方法成功做出来了,可是不知道为什么在文章里要刷新一遍才会出现toc,想问一下大佬有什么解决方法吗
博主 FH博客
你好,请问一下您博客的模板可以给的吗
博主 rm -rt /
1
博主 moke
想问一下大佬你的框框包裹的代码是啥?
博主 Phower
很喜欢这个toc,这两天在我那儿又捣鼓了一下,CSS悬挂的话可以用position:sticky,加个wrap包裹一下就ok了,非常方便
博主 Mashiro
@Phower 可以的
博主 蝉時雨
之前做博客测试的时候也写过一个类似的简单TOC,后来又觉得没必要去掉了~~~
博主 Mashiro
@蝉時雨 哈哈手闲加的,等我以后长篇大论的时候就有意义了?
博主 惶心
另外Google Font 反代建议用 fonts.cat.net 的,国内外都非常快
博主 Mashiro
@惶心 不太喜欢用私服镜像~ 中科大的我觉得还行~
博主 广树
很有用马克下
博主 惶心
先加入Google Keep,回头再看
博主 Mashiro
@惶心 我常用Pocket
博主 惶心
@Mashiro 突然发现你的ip库貌似不太准确qwq
博主 Mashiro
@惶心 IPv6的定位不太好搞,嘛,反正你这个也是DigitalOcean的随机IP,乱跑也正常