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和DOM Interfaces来处理HTML
Oct 09 Javascript
浅析Prototype的模板类 Template
Dec 07 Javascript
jquery全选/全不选/反选另一种实现方法(配合原生js)
Apr 07 Javascript
html5的自定义data-*属性和jquery的data()方法的使用示例
Aug 21 Javascript
怎么判断js脚本加载完成
Feb 28 Javascript
JavaScript将字符串转换成字符编码列表的方法
Mar 19 Javascript
在Javascript中处理数组之toSource()方法的使用
Jun 09 Javascript
JS如何实现文本框随文本的长度而增长
Jul 30 Javascript
Javascript 字符串模板的简单实现
Feb 13 Javascript
jQuery回到顶部的代码
Jul 09 Javascript
jQuery右下角悬浮广告实例
Oct 17 Javascript
JavaScript中的事件与异常捕获详析
Feb 24 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
一个MYSQL操作类
2006/11/16 PHP
Admin generator, filters and I18n
2011/10/06 PHP
php+mysql+jquery实现简易的检索自动补全提示功能
2017/04/15 PHP
PHP递归算法的简单实例
2019/02/28 PHP
PHP7内核之Reference详解
2019/03/14 PHP
javascript 表格排序和表头浮动效果(扩展SortTable)
2009/04/07 Javascript
基于jquery的自定义鼠标提示效果 jquery.toolTip
2010/11/14 Javascript
简介JavaScript中setUTCSeconds()方法的使用
2015/06/12 Javascript
javascript瀑布流式图片懒加载实例
2020/06/28 Javascript
JS控制弹出悬浮窗口(一览画面)的实例代码
2016/05/30 Javascript
jQuery 利用$.ajax 时获取原生XMLHttpRequest 对象的方法
2016/08/25 Javascript
JS中检测数据类型的几种方式及优缺点小结
2016/12/12 Javascript
Angularjs通过指令监听ng-repeat渲染完成后执行脚本的方法
2016/12/31 Javascript
vue.js开发环境安装教程
2017/03/17 Javascript
javascript 判断一个对象为数组的方法
2017/05/03 Javascript
jQuery实现验证用户登录
2019/12/10 jQuery
js实现全选和全不选功能
2020/07/28 Javascript
JavaScript实现4位随机验证码的生成
2021/01/28 Javascript
python实现数通设备tftp备份配置文件示例
2014/04/02 Python
python基于xml parse实现解析cdatasection数据
2014/09/30 Python
python 字典 setdefault()和get()方法比较详解
2019/08/07 Python
利用python实现短信和电话提醒功能的例子
2019/08/08 Python
Flask中endpoint的理解(小结)
2019/12/11 Python
Python autoescape标签用法解析
2020/01/17 Python
美国高端婴童品牌:Hanna Andersson
2016/10/30 全球购物
ALDO加拿大官网:加拿大女鞋品牌
2018/12/22 全球购物
会议接待欢迎词
2014/01/12 职场文书
高一英语教学反思
2014/01/22 职场文书
初中化学教学反思
2014/01/23 职场文书
农民工创业典型事迹
2014/01/25 职场文书
公司业务员岗位职责
2014/03/18 职场文书
《小鹰学飞》教学反思
2014/04/23 职场文书
2015年六一儿童节活动方案
2015/05/05 职场文书
2015年医院保卫科工作总结
2015/07/23 职场文书
2017春节晚会开幕词
2016/03/03 职场文书
pytest实现多进程与多线程运行超好用的插件
2022/07/15 Python