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 相关文章推荐
javascript之解决IE下不渲染的bug
Jun 29 Javascript
JavaScript几种形式的树结构菜单
May 10 Javascript
jQuery fadeTo方法调整图片的透明度使用介绍
May 06 Javascript
JavaScript DOM操作表格及样式
Apr 13 Javascript
JavaScript电子时钟倒计时第二款
Jan 10 Javascript
js下将金额数字每三位一逗号分隔
Feb 19 Javascript
详解从新建vue项目到引入组件Element的方法
Aug 29 Javascript
webpack的CSS加载器的使用
Sep 11 Javascript
微信小程序视图控件与bindtap之间的问题的解决
Apr 08 Javascript
微信小程序渲染性能调优小结
Jul 30 Javascript
vue中使用v-model完成组件间的通信
Aug 22 Javascript
36个正则表达式(开发效率提高80%)
Nov 17 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
Array of country list in PHP with Zend Framework
2011/10/17 PHP
ThinkPHP实现支付宝接口功能实例
2014/12/02 PHP
window.open被浏览器拦截后的自定义提示效果代码
2007/11/19 Javascript
JavaScript 解析Json字符串的性能比较分析代码
2009/12/16 Javascript
jQuery实现form表单reset按钮重置清空表单功能
2012/12/18 Javascript
js全屏显示显示代码的三种方法
2013/11/11 Javascript
js使下拉列表框可编辑不止是选择
2013/12/12 Javascript
Linux下使用jq友好的打印JSON技巧分享
2014/11/18 Javascript
Javascript解析URL方法详解
2014/12/05 Javascript
window.onload使用指南
2015/09/13 Javascript
js电话号码验证方法
2015/09/28 Javascript
悬浮广告方法日常收集整理
2016/03/18 Javascript
基于javascript实现样式清新图片轮播特效
2016/03/30 Javascript
关于JavaScript和jQuery的类型判断详解
2016/10/08 Javascript
js对字符串进行编码的方法总结(推荐)
2016/11/10 Javascript
js实现微信/QQ直接跳转到支付宝APP打开口令领红包功能
2018/01/09 Javascript
在vue中根据光标的显示与消失实现下拉列表
2019/09/29 Javascript
微信小程序返回箭头跳转到指定页面实例解析
2019/10/08 Javascript
详解python如何调用C/C++底层库与互相传值
2016/08/10 Python
python 系统调用的实例详解
2017/07/11 Python
TensorFlow实现RNN循环神经网络
2018/02/28 Python
python将每个单词按空格分开并保存到文件中
2018/03/19 Python
Python使用matplotlib绘制三维图形示例
2018/08/25 Python
Python unittest 简单实现参数化的方法
2018/11/30 Python
Python控制Firefox方法总结
2019/06/03 Python
Python如何应用cx_Oracle获取oracle中的clob字段问题
2019/08/27 Python
Python实现迪杰斯特拉算法过程解析
2020/09/18 Python
Html5画布_动力节点Java学院整理
2017/07/13 HTML / CSS
美国在线咖啡、茶和餐厅供应商:LollicupStore
2018/05/04 全球购物
中华魂演讲稿
2014/05/13 职场文书
企业职业病防治方案
2014/05/29 职场文书
经济信息系毕业生自荐信
2014/06/02 职场文书
股东授权委托书范本
2014/09/13 职场文书
2014年宣传部个人工作总结
2014/12/06 职场文书
学雷锋献爱心倡议书
2015/04/27 职场文书
2015高中教师个人工作总结
2015/07/21 职场文书