hexo博客实现url锚点与markdown分级标题同步

起因

有的网页页面,可以在页面滚动的时候,给 url 自动添加或者改变锚点,锚点内容即为当前页面正在显示的内容的分级标题。我希望自己的博客页面也能有这样的效果,这样的话,复制 url 的时候就可以带上锚点,打开后就能快速定位到需要查看的内容处。

分析

要实现这个功能,做到以下两点就可以了

  1. 判断位于当前浏览器视口顶部的元素属于哪一个分级标题的内容
  2. 将当前分级标题的 id 名作为锚点添加或修改到 url 尾部

打开控制台,可以看到,hexo next 的主题,通过 markdown 渲染出的内容,包含在 classpost-block 的元素中。

而所有的分级标题渲染出来后,会依次对应 htmlh1h6 标签,且 id 名与标题等同。(不过空格会被替换为 - )

实现

判断分级标题的内容

我们可以通过 Element.getBoundingClientRect().top 来获取一个 dom 元素的顶部与视口顶部的距离,如果得到一个负值,那么证明这个元素是在视口之上、或者正处于视口中。我们要的就是处于视口上的那个元素。获取它的 id 即可。

修改 URL,添加或者改变锚点内容

利用 HTML5 的历史记录 API,history.replaceState,它可以修改当前历史记录项,改变 url 信息,但却不会引起 url 跳转。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 按从上到下的先后顺序,获取 .post-body 里面所有的 h 标签
let hList = document.querySelectorAll('.post-body h1,.post-body h2,.post-body h3,.post-body h4,.post-body h5,.post-body h6');
let nowAnchor = null;
let time = 0;

window.addEventListener('scroll', function (e) {
// 事件节流,每 100ms 进行一次判断
if (Date.now() - time >= 100) {
// 逆向查找,找到的第一个负值所对应的元素即为我们需要的
for (let i = hList.length - 1; i >= 0; i--) {
if (hList[i].getBoundingClientRect().top <= 0) {
if (hList[i].id !== nowAnchor) {
let href = location.href.includes('#') ? location.href.split('#')[0] : location.href;
nowAnchor = hList[i].id;
history.replaceState({}, '', `${href}#${nowAnchor}`);
}
break;
}
}
time = Date.now();
}
});

后记

虽然实现了我想要的这个功能,可是在每一次 url 更新的时候,hexo next 自带配置的 pace 加载进度条特效也会重新进行一次,而我只希望他只在初次打开的时候有特效,更新锚点的时候不要搞事。搞了一阵子,失败了,忍痛关闭此特效功能。

如果有大佬有好的解决办法,望告知,感激不尽!

参考资料

MDN: Element.getBoundingClientRect()
MDN: History_API

好饿,想吃两个小饼饼(# ̄y▽ ̄)╭
0%