js实现文章目录索引导航(table of content)


Posted in Javascript onMay 10, 2020

什么叫TOC呢?table of content。

具体什么效果呢?可以随便找个hexo博客中体验一下,例如这个。

好了,实现它有2个要点:

点目录跳到段落:通过<a>标签的锚点实现,其原理在这里。
滚动触发目录变换:通过js监听滚动事件,判定当前所处段落,令对应目录项高亮。
我写了一个简单的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靠左,当前目录高亮为红色,正文则靠右填满屏幕:

#toc {
   width: 200px;
   position: fixed;
   left: 0;
   top: 0;
  }
  #toc a.active {
   color: red;
  }
  #content {
   margin-left: 200px;
  }

在上面的静态页面中,目录暂时为空,因为需要用JS动态生成。

正文中需要人工埋点段落起始标识,也就是a.seg-begin这样的锚点,每个段落的锚点name唯一,而锚点之后紧随段落的内容。

在JS中,我首先按锚点的出现次序收集所有的a.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事件进行监听,每次滚动就判断最近一个滚出屏幕顶部的a.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())
     }
    })

后续

这里目录的生成是在前端JS里根据正文的锚点动态生成的,为了SEO可以在后端提交文章正文时匹配出这些锚点,直接保存为目录。

完整代码

<!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来表达层级的,这样在each遍历生成目录的时候可以基于这个信息完成嵌套层级的标记,问题迎刃而解。

Javascript 相关文章推荐
使用js获取QueryString的方法小结
Feb 28 Javascript
jquery ui resizable bug解决方法
Oct 26 Javascript
JS随机生成不重复数据的实例方法
Jul 17 Javascript
浅谈javascript实现八大排序
Apr 27 Javascript
js实现创建删除html元素小结
Sep 30 Javascript
JS获取月份最后天数、最大天数与某日周数的方法
Dec 08 Javascript
vue双向绑定简要分析
Mar 23 Javascript
原生JS实现N级菜单的代码
May 21 Javascript
使用 vue 实例更好的监听事件及vue实例的方法
Apr 22 Javascript
vue实现手机号码的校验实例代码(防抖函数的应用场景)
Sep 05 Javascript
vue弹出框组件封装实例代码
Oct 31 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
May 31 Javascript
文章或博客自动生成章节目录索引(支持三级)的实现代码
May 10 #Javascript
vue滑动吸顶及锚点定位的示例代码
May 10 #Javascript
webpack+vue.js构建前端工程化的详细教程
May 10 #Javascript
JS实现单张或多张图片持续无缝滚动的示例代码
May 10 #Javascript
Layer UI表格列日期格式化及取消自动填充日期的实现方法
May 10 #Javascript
angula中使用iframe点击后不执行变更检测的问题
May 10 #Javascript
JavaScript 装逼指南(js另类写法)
May 10 #Javascript
You might like
php连接mssql的一些相关经验及注意事项
2013/02/05 PHP
PHP把网页保存为word文件的三种方法
2014/04/01 PHP
php分页示例分享
2014/04/30 PHP
PHP实现自动对图片进行滚动显示的方法
2015/03/12 PHP
在Thinkphp中使用ajax实现无刷新分页的方法
2016/10/25 PHP
如何优雅的使用 laravel 的 validator验证方法
2018/11/11 PHP
PHP PDOStatement::setFetchMode讲解
2019/02/03 PHP
弹出广告特效(一个IP只弹出一次)的代码
2007/07/27 Javascript
JavaScript的public、private和privileged模式
2009/12/28 Javascript
利用js获取服务器时间的两个简单方法
2010/01/08 Javascript
JavaScript Perfection kill 测试及答案
2010/03/23 Javascript
js parentElement和offsetParent之间的区别
2010/03/23 Javascript
让浏览器非阻塞加载javascript的几种方法小结
2011/04/25 Javascript
JavaScript面向对象程序设计三 原型模式(上)
2011/12/21 Javascript
datagrid框架的删除添加与修改
2013/04/08 Javascript
js confirm()方法的使用方法实例
2013/07/13 Javascript
Jquery倒计时源码分享
2014/05/16 Javascript
JavaScript点击按钮后弹出透明浮动层的方法
2015/05/11 Javascript
JS动态加载脚本并执行回调操作
2016/08/24 Javascript
js实现本地时间同步功能
2017/08/26 Javascript
Angularjs实现多图片上传预览功能
2018/07/18 Javascript
vue2.x集成百度UEditor富文本编辑器的方法
2018/09/21 Javascript
爬虫利器Puppeteer实战
2019/01/09 Javascript
python DataFrame获取行数、列数、索引及第几行第几列的值方法
2018/04/08 Python
python实现从pdf文件中提取文本,并自动翻译的方法
2018/11/28 Python
浅谈Python2之汉字编码为unicode的问题(即类似\xc3\xa4)
2019/08/12 Python
python elasticsearch环境搭建详解
2019/09/02 Python
python批量将excel内容进行翻译写入功能
2019/10/10 Python
python中68个内置函数的总结与介绍
2020/02/24 Python
Python virtualenv虚拟环境实现过程解析
2020/04/18 Python
英国领先的露营和露营车品牌之一:OLPRO
2019/08/06 全球购物
十月份红领巾广播稿
2014/01/22 职场文书
四风问题自查报告剖析材料
2014/02/08 职场文书
幼教求职信
2014/03/12 职场文书
公司仓管员岗位职责
2015/04/01 职场文书
Python编程中内置的NotImplemented类型的用法
2022/03/23 Python