对于一些技术类的文章,文章目录是比较需要的,于是在Aria v1.8.4
加入了这一个功能。
前言
只考虑在前端对文章内容解析以构建目录,之前参考的文章是这一篇JS生成文章目录 - lxjwlt's blog
这篇文章的代码还是比较不错的,但是考虑到诸多原因,例如子目录自动开合、滚动监听等功能以及我的需求,还是感觉比较繁杂。于是我最终还是选择了使用tocbot这一项目来构建文章目录。
资源引入
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.5.0/tocbot.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.5.0/tocbot.css">
注意到在这个教程里,tocbot.min.js
的版本必须>=4.4.4
。下文会有解释
构建目录
标题锚点
需要注意的是,要使文章目录点击标题跳转生效,在文章中的h1,h2,h3,h4,h5,h6
的标题必须要有id
这一属性,也就是要提供一个锚点。例如:
<h1 id="TOC-build-toc">构建目录</h1>
<h2 id="TOC-test">测试</h2>
<!-- ... -->
但是既然是在typecho这个平台上写文章,那自然大部分人会优先使用Markdown语法进行书写。
# 标题1
## 标题2
### 标题3
然而对于typecho默认的Markdown解析器HyperDown,我在其Parser.php
中并未找到给标题添加id
这一属性的代码。
也就是说,给标题增加id
属性这一工作了得自己来完成。
我在此采用的是前端的方法,后端当然也可以。
一些问题的考虑:
- 标题id中最好不要有符号或其他特殊字符
- 给重复的id增加一个索引号以区别
出于这些考虑,就有了如下增加id
的代码
var headerEl = 'h1,h2,h3,h4', //headers
content = '.post-content',//文章容器
idArr = {}; //标题数组以确定是否增加索引id
//add #id
$(content).children(headerEl).each(function () {
//去除空格以及多余标点
var headerId = $(this).text().replace(/[\s|\~|`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\||\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?|\:|\,|\。]/g, '');
headerId = headerId.toLowerCase();
if (idArr[headerId]) {
//id已经存在
$(this).attr('id', headerId + '-' + idArr[headerId]);
idArr[headerId]++;
}
else {
//id未存在
idArr[headerId] = 1;
$(this).attr('id', headerId);
}
});
这段理解起来比较容易,在这里就不解释了。区别重复id的方法当然不止这一种,我只是选择了比较快捷简便的一种方法。
初始化
官方给出的比较简单的初始化方法:
tocbot.init({
// 构建目录的容器
tocSelector: '.js-toc',
// 文章容器
contentSelector: '.js-toc-content',
// 需要解析的标题
headingSelector: 'h1, h2, h3',
});
而根据我的需求,我的初始化方法如下:
var headerEl = 'h1,h2,h3,h4', //headers
content = '.post-content',//文章容器
idArr = {}; //标题数组以确定是否增加索引id
tocbot.init({
tocSelector: '.toc',
contentSelector: content,
headingSelector: headerEl,
//positionFixedSelector: '.toc',
//positionFixedClass: 'is-position-fixed',
//fixedSidebarOffset: 'auto',
scrollSmooth: true,
scrollSmoothOffset: -80,
headingsOffset: -500
});
关于positionFixedSelector
/positionFixedClass
/fixedSidebarOffset
/scrollSmooth
/scrollSmoothOffset
/headingsOffset
这几个参数我会在下文进行解析。
PJAX回调
这里通过jquery-pjax
进行举例:
$(document).on('pjax:send',function(){
//destroy()方法
if ($('.toc').length) tocbot.destroy();
})
$(document).on('pjax:complete',function(){
//再调用一次toc.init()方法
//例如
toc.init(tocOptions);
})
到此,基本目录已经构建完成了(如果不嫌比较糙的话。。。)。
进阶
目录滚动跟随
与此相关的参数有positionFixedSelector
/positionFixedClass
/fixedSidebarOffset
positionFixedSelector - Element to add the positionFixedClass to.
positionFixedClass - Fixed position class to add to make sidebar fixed after scrolling down past the fixedSidebarOffset.
fixedSidebarOffset - fixedSidebarOffset can be any number but by default is set to auto which sets the fixedSidebarOffset to the sidebar element's offsetTop from the top of the document on init.
要实现滚动跟随的话,你可以如下配置,例如:
positionFixedSelector: '.toc',
positionFixedClass: 'is-position-fixed',
fixedSidebarOffset: 'auto',
第三项一般设置为auto
即可,在目录不可见时,tocbot会为.toc
的元素加上is-position-fixed
这一class
。
.is-position-fixed {
position:fixed !important;
top:0
}
当然is-position-fixed
的css你可以自行配置,tocbot默认的如上。
也就是说,当目录不可见的时候,目录会变为fixed
,跟着屏幕进行滚动。
但是我采用的是sticky
方法,所以我上面三项配置都注释掉了。
其实并不推荐使用sticky
,按照MDN的说法
“这是一个实验性的 API,请尽量不要在生产环境中使用它。”
然而我比较喜欢偷懒....
跳转偏移
对于跳转到某一标题,有一个比较常见的问题:跳转到对应标题后,浮动的导航栏(或者是别的元素)可能会挡住你的标题(#21)
对于这个问题,有两种方法解决:
- 使用
scrollEndCallback
配置项手动编写跳转后偏移的代码- 使用
scrollSmoothOffset
这一配置参数
之前我采用的是第一种方法,但是感觉效果并不好,原因是跳转之后你的滚动条会再次滚一次来达到偏移。在tocbot>=4.4.4
版本之后,项目的作者为其加上了scrollSmoothOffset
。
这个参数的作用就是在跳转同时进行一定的偏移,也就是说直接跳转到偏移之后的位置,而不是跳转到标题之后再进行偏移(滚动条只滚动一次),体验个人感觉比第一种方法好。
这一参数对应了scroll-smooth的offset
参数,但是tocbot项目的README中似乎并未加上这一参数的说明。
注意要将smoothScroll
这一项配置为true
滚动监听
偏移已经完成了,但是又碰上了一个新的问题:
在滚动到对应的标题的时候,目录会监听当前的位置对目录中的标题进行高亮/突出显示。但是如果两个标题靠太近,或者你对跳转时做了偏移处理,此时目录突出显示的标题也许并不会与文章中的标题对应上。
例如下图,本应该高亮显示主体部分解析的标题(已做了偏移),但是却突出显示了error 部分
这一标题。
此时,headingsOffset
就派上了用场。
但是这个值应该如何设置呢?在tocbot项目的build-html.js
中的165-174行左右可以看到:
if (heading.offsetTop > top + options.headingsOffset + 10) {
// Don't allow negative index value.
var index = (i === 0) ? i : i - 1
topHeader = headings[index]
return true
} else if (i === headings.length - 1) {
// This allows scrolling for the last heading on the page.
topHeader = headings[headings.length - 1]
return true
}
也就是说,满足heading.offsetTop > top + options.headingsOffset + 10
这一条件,高亮的标题才会被更新。那么headingOffset
在这种需求下应该设为负值。而这个值具体设置为多少,可以自己尝试。
代码参考
if ($('.toc').length > 0) {
var headerEl = 'h1,h2,h3,h4', //headers
content = '.post-content',//文章容器
idArr = {}; //标题数组以确定是否增加索引id
//add #id
$(content).children(headerEl).each(function () {
//去除空格以及多余标点
var headerId = $(this).text().replace(/[\s|\~|`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\||\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?|\:|\,|\。]/g, '');
headerId = headerId.toLowerCase();
if (idArr[headerId]) {
//id已经存在
$(this).attr('id', headerId + '-' + idArr[headerId]);
idArr[headerId]++;
}
else {
//id未存在
idArr[headerId] = 1;
$(this).attr('id', headerId);
}
});
tocbot.init({
// Where to render the table of contents.
tocSelector: '.toc',
// Where to grab the headings to build the table of contents.
contentSelector: content,
// Which headings to grab inside of the contentSelector element.
headingSelector: headerEl,
//positionFixedSelector: '.toc',
//positionFixedClass: 'is-position-fixed',
scrollSmooth: true,
scrollSmoothOffset: -80,
headingsOffset: -500
});
}
老哥你那个文章目录解析两怎么弄的啊
By 叶子 at 2019-4-30 05:47 pm.
@叶子
具体指的是什么呢?
By Siphils at 2019-5-16 06:58 pm.
@Siphils
大佬可以不可以弄一个悬浮的文章解析,可以收缩的那种
By 叶子 at 2019-5-16 07:06 pm.
@叶子
我这个博客正在测试的就是有这个功能的,等我有空更新下个版本吧OωO
By Siphils at 2019-5-16 07:11 pm.
@Siphils
By 叶子 at 2019-5-16 07:13 pm.
学习了,谢谢博主
By 知识共享网 at 2019-4-23 02:29 pm.
文章不错非常喜欢
By 今日新闻头条 at 2019-4-11 12:06 am.
路过
By 千城丿墨染 at 2019-3-27 11:44 am.
我还帮tocbot添加过一个scrollContainer选项呢|´・ω・)ノ
By Mashiro at 2019-3-22 01:23 am.
@Mashiro
|´・ω・)ノ 我有看到!
By Siphils at 2019-3-24 09:29 am.
其实我二开的时候也关注过 在页面尺寸较大的情况下 顶部导航栏的问题 目前这个id定位还是有问题 比如说定位以后 左侧的侧边栏还是显示上一层的部分 并不会显示下一层就比方说 当前文章的目录 点击构建目录以后 加粗部分的样式依旧在资源引入上 只有把 构建目录的标签滚到浏览器顶部的时候才会显示到构建目录上 我对前端只有半桶水的水平 感觉大佬可以在顶部导航栏这部分有所操作 想办法在点击侧边栏的时候可以隐藏顶部导航栏
By 怼 at 2019-3-16 11:53 pm.
@怼
刚刚重新测试了下,将
headingsOffset
这一项再微调下就好了。关于点击侧边栏隐藏导航栏的问题,用这个tocbot的话可以在scrollEndCallback里把顶部导航对应的显示的class修改下,就可以滚动完成后隐藏顶部导航。1.9.1这个我还没push,之前的目录构建都不是用的tocbot
By Siphils at 2019-3-17 10:06 am.
@Siphils
我觉得吧 关于这个顶部导航栏 还是做成点击侧边栏后直接隐藏的好 我自己就是点击侧边栏然后顶部直接隐藏 这样侧边栏加粗显示的字体也是正确的
By 怼 at 2019-3-17 02:52 pm.
@怼
试了很多方法,还是不能很好地搞定,先搁置了有新思路再解决。有别的事要忙。
By Siphils at 2019-3-17 08:28 pm.
@Siphils
渣渣代码在此
By 怼 at 2019-3-17 08:38 pm.
@Siphils
我开始二开的时候用的是1.8.4的资源 对于这一块直接加了修改class的代码 所以我改动以后还没发现什么大问题至于1.9.0的嘛。。。。我可能会在整个网站功能写的差不多了再慢慢更新样式了
By 怼 at 2019-3-17 08:35 pm.
@怼
旧版本目录的功能其实不太全,新版本tocbot实现的(也就是现在这个)好一些。
By Siphils at 2019-3-17 08:59 pm.
@怼
不同分辨率会有影响的...
By Siphils at 2019-3-17 08:01 pm.
@怼
不同分辨率下还是有问题,多谢提醒,我再想想看
By Siphils at 2019-3-17 09:39 am.
这个侧边栏跟之前的区别就是子选项会隐藏 然后每个选项最左侧不再有数字显示 在前台展示部分 和1.8.4是不是没有什么区别
By 怼 at 2019-3-16 11:47 pm.
@怼
代码变了...
By Siphils at 2019-3-17 09:38 am.
https://2heng.xin/2018/02/13/add-a-toc-for-your-article/
请问这个博客是什么系统的呀
By 浮华博客 at 2019-3-16 07:29 am.
@浮华博客
wordpress博客
By Siphils at 2019-3-16 09:39 pm.
厉害了博主,技术牛
By 楚狂人 at 2019-3-13 05:13 pm.