原因

:!(彩虹) 在使用 typecho 编辑主题的时候碰到一个问题,就是在文章的页面中如何去自动加上目录链接。

遇到的问题

一开始想到的是找相关的插件作为辅助,但是来回兜了几圈之后发现,别人写的 PHP 插件版本太老了,而且样式也不是很OK不美观,如果是正对源码去修改的话也是很困难的,本人也没学过 PHP 代码,看起来别说多费劲了,边看边猜测。第二个,我也有想到去找一些前端目录的实现,结果也可想而之,不好看也达不到我要的功能。

针对上面的问题,索性我直接从前端手写一个简单的 JS 封装直接调用就好,特此记录这篇供大家参考:

编写目录

在我们的文章中,如果是在编辑器编写的文章带标题的话,是会有  h1, h2, h3... 之类的标签的,类似下图的标签展示,思考一下,我们怎么去拿到文章内的标签呢?这里我选择使用 DOM 节点的遍历。

  1. 可以先获取到整个文章的内容,然后再拿到内容下所有的节点。
  2. 拿到节点之后,可以使用 for 循环或者 foreach 遍历节点,其中我们遍历是需要条件。
  3. 遍历的条件是是否包含 h1h2 ..等之类的节点 ,然后把符合的节点都添加到一个新的数组中保存。
  4. 再对节点进行第二次的筛选,相当于把第三步获取到的数据再重新排列,因为之前拿到的数据格式是一维数组,这样的格式不是我想要的(设想的格式是,如果有多少类型的标题就创建几维数组),这样后面输出的时候也方便还可以加上我想要的
  5. 接上第四步获取到的结果,使用 for 循环拼接字符串,因为我没有用到 VUE 所以直接拼接即可,循环的时候加上索引即可。

WX20210630-172705 (1).png

具体代码实现

上代码,先获取节点。

  // 获取文章内节点(把这个换成你们自己的 id 或者 class )
  var content = document.querySelector('.y-detail .content');
  // 需要查找的所有节点
  var nodes = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
  // 需要保持的数组
  var titles = [];
  // 遍历节点内容
  content.childNodes.forEach(function (item, index) {
    // 当前节点是否包含查找的节点
    if (nodes.includes(item.nodeName)) {
      // 给查找的节点添加一个 id 标示
      var id = "header-" + index;
      item.setAttribute("id", id);
      // 定义一个节点的等级 1 2,3,4...
      var level = Number(item.nodeName.substring(1, 2));
      // 定义当前的节点的父节点
      var parent = level - 1;
      item.level = level;
      item.parent = parent;
      // 保存到数组中
      titles.push({
        id: id,
        title: item.innerHTML,
        level: item.level,
        parent: item.parent,
        nodeName: item.nodeName,
        child: []
      })
    }
  });

里面设置的 ID 是为了在点击的时候获取锚点去滚动到具体文章的具体位置。


第二步为了重新筛选数据。

  var map = {};
  // 新的存储数组
  var roots = [];
  // 第一次获取的标签属性(很重要),以这个标签为最大标签,依次获取下级标签
  // 如果下级 h 标签比第一次获取的打就不作操作,抛出异常
  var firstParent = titles[0] && titles[0].parent;
  // 如果没有标签就直接返回,不做操作
  if(!firstParent) return
  titles.forEach(function(item, index) {
    // 把自己的出现的索引保存上
    map[item.level] = index
    if(item.parent != firstParent) {
      // 如果是没找到的情况就跑出异常
      if (typeof map[item.parent] !== 'undefined') {
        titles[map[item.parent]].child.push(item)
      } else {
        roots.push({
          id: item.id,
          title: '目录生成错误,请检查!'
        });
      }
    } else {
      roots.push(item)
    }
  })

最后再把新拿到的数据循环重新拼接到 HTML 中。
具体代码我就不多写了,我把文件放到文章下面,自行下载,然后把里面的代码放到你的页面上,就可以了。
最后呈现结果就如本页面展示的,当然,你得美化一下你的样式。

{anote icon="fa-download" href="https://luszy.com/download/catalogue.zip" type="secondary" content="下载"/}