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 相关文章推荐
层序遍历在ExtJs的TreePanel中的应用
Oct 16 Javascript
基于jquery的弹出提示框始终处于窗口的居中位置(类似于alert弹出框的效果)
Sep 28 Javascript
js单例模式的两种方案
Oct 22 Javascript
js格式化时间和js格式化时间戳示例
Feb 10 Javascript
Flash图片上传组件 swfupload使用指南
Mar 14 Javascript
jQuery模拟淘宝购物车功能
Feb 27 Javascript
vue的全局提示框组件实例代码
Feb 26 Javascript
vue2.0实现音乐/视频播放进度条组件
Jun 06 Javascript
AngularJS实现动态切换样式的方法分析
Jun 26 Javascript
微信小程序实现指定显示行数多余文字去掉用省略号代替
Jul 25 Javascript
基于javascript的拖拽类封装详解
Apr 19 Javascript
解决vue路由name同名,路由重复的问题
Aug 05 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
PHP5.2中date()函数显示时间与北京时间相差8小时的解决办法
2009/05/28 PHP
队列在编程中的实际应用(php)
2010/09/04 PHP
PHP5 的对象赋值机制介绍
2011/08/02 PHP
php empty()与isset()区别的详细介绍
2013/06/17 PHP
php 常用算法和时间复杂度
2013/07/01 PHP
php的sso单点登录实现方法
2015/01/08 PHP
Ajax和PHP正则表达式验证表单及验证码
2016/09/24 PHP
PHP goto语句用法实例
2019/08/06 PHP
JavaScript 计算当天是本年本月的第几周
2009/03/22 Javascript
javascript html 静态页面传参数
2009/04/10 Javascript
用js实现in_array的方法
2013/11/05 Javascript
js实现ArrayList功能附实例代码
2014/10/29 Javascript
JS随机打乱数组的方法小结
2016/06/22 Javascript
js轮播图代码分享
2016/07/14 Javascript
javascript中的try catch异常捕获机制用法分析
2016/12/14 Javascript
使用jquery的cookie实现登录页记住用户名和密码的方法
2019/03/13 jQuery
layuiAdmin循环遍历展示商品图片列表的方法
2019/09/16 Javascript
Vue插件之滑动验证码
2019/09/21 Javascript
python3.5仿微软计算器程序
2020/03/30 Python
python itchat实现微信好友头像拼接图的示例代码
2017/08/14 Python
Python实现学生成绩管理系统
2020/04/05 Python
python散点图实例之随机漫步
2018/08/27 Python
python使用folium库绘制地图点击框
2018/09/21 Python
Python Selenium 之关闭窗口close与quit的方法
2019/02/13 Python
Python 3 使用Pillow生成漂亮的分形树图片
2019/12/24 Python
Tensorflow实现部分参数梯度更新操作
2020/01/23 Python
Python之字典添加元素的几种方法
2020/09/30 Python
Pycharm安装Qt Design快捷工具的详细教程
2020/11/18 Python
匡威西班牙官网:Converse西班牙
2019/10/01 全球购物
NICKIS.com荷兰:设计师儿童时装
2020/01/08 全球购物
二手书店创业计划书
2014/01/16 职场文书
党的群众路线教育实践活动动员会主持词
2014/03/20 职场文书
有子女的离婚协议书怎么写(范本)
2014/09/29 职场文书
MySQL kill不掉线程的原因
2021/05/07 MySQL
教你怎么用Python操作MySql数据库
2021/05/31 Python
关于Redis的主从复制及哨兵问题
2022/06/16 Redis