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 相关文章推荐
jquery attr 设定src中含有&amp;(宏)符号问题的解决方法
Jul 26 Javascript
JS获得URL超链接的参数值实例代码
Jun 21 Javascript
js data日期初始化的5种方法
Dec 29 Javascript
JQuery EasyUI的使用
Feb 24 Javascript
给easyui datebox扩展一个清空的实例
Nov 09 Javascript
javascript使用递归算法求两个数字组合功能示例
Jan 03 Javascript
jQuery实现文字自动横移
Jan 08 Javascript
分享一道关于闭包、bind和this的面试题
Feb 20 Javascript
vue实现表格增删改查效果的实例代码
Jul 18 Javascript
Layui带搜索的下拉框的使用以及动态数据绑定方法
Sep 28 Javascript
将RGB值转换为灰度值的简单算法
Oct 09 Javascript
如何将Node.js中的回调转换为Promise
Nov 10 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中抽象类和接口的概念以及区别
2013/06/27 PHP
PHP遍历某个目录下的所有文件和子文件夹的实现代码
2013/06/28 PHP
PHP获取MAC地址的具体实例
2013/12/13 PHP
Symfony2使用Doctrine进行数据库查询方法实例总结
2016/03/18 PHP
PHP构造函数与析构函数用法示例
2016/09/28 PHP
全面解析PHP面向对象的三大特征
2017/06/10 PHP
Thinkphp 框架扩展之Widget扩展实现方法分析
2020/04/23 PHP
短信提示使用 特效
2007/01/19 Javascript
Javascript匿名函数的一种应用 代码封装
2010/06/27 Javascript
jQuery :first选择器使用介绍
2013/08/09 Javascript
5种处理js跨域问题方法汇总
2014/12/04 Javascript
JavaScript实现梯形乘法表的方法
2015/04/25 Javascript
基于JavaScript代码实现自动生成表格
2016/06/15 Javascript
Backbone View 之间通信的三种方式
2016/08/09 Javascript
Three.js基础部分学习
2017/01/08 Javascript
Angular.JS内置服务$http对数据库的增删改使用教程
2017/05/07 Javascript
Vue 创建组件的两种方法小结(必看)
2018/02/23 Javascript
Python写的贪吃蛇游戏例子
2014/06/16 Python
python处理大数字的方法
2015/05/27 Python
Python简单调用MySQL存储过程并获得返回值的方法
2015/07/20 Python
python 垃圾收集机制的实例详解
2017/08/20 Python
Python实现压缩和解压缩ZIP文件的方法分析
2017/09/28 Python
Python文件读写保存操作的示例代码
2018/09/14 Python
在pycharm中python切换解释器失败的解决方法
2018/10/29 Python
Python 文件操作之读取文件(read),文件指针与写入文件(write),文件打开方式示例
2019/09/29 Python
关于Python-faker的函数效果一览
2019/11/28 Python
html5 学习简单的拾色器
2010/09/03 HTML / CSS
Kipling凯浦林美国官网:世界著名时尚休闲包袋品牌
2016/08/24 全球购物
李维斯德国官方网上商店:Levi’s德国
2016/09/10 全球购物
美国礼品卡交易网站:Cardpool
2018/08/27 全球购物
会计助理岗位职责
2014/02/17 职场文书
关于九一八事变的演讲稿2014
2014/09/17 职场文书
起诉书格式范文
2015/05/20 职场文书
烛光里的微笑观后感
2015/06/17 职场文书
幼儿园音乐教学反思
2016/02/18 职场文书
2019财务毕业实习报告
2019/06/27 职场文书