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数组的排序 sort()方法和reverse()方法
Jun 04 Javascript
javascript 保存文件到本地实现方法
Nov 29 Javascript
jQuery实现回车键(Enter)切换文本框焦点的代码实例
May 05 Javascript
一个检测表单数据的JavaScript实例
Oct 31 Javascript
jQuery Validate验证框架经典大全
Sep 23 Javascript
深入探讨javascript函数式编程
Oct 11 Javascript
Bootstrap实现input控件失去焦点时验证
Aug 04 Javascript
输入法的回车与消息发送快捷键回车的冲突解决方法
Aug 09 Javascript
Vue如何实现组件的源码解析
Jun 08 Javascript
Vim快速合并行及vim 将文件所有行合并到一行
Nov 27 Javascript
微信小程序实现流程进度的图样式功能
Jan 16 Javascript
JS关于刷新页面的相关总结
May 09 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时间戳转换的示例
2014/03/31 PHP
PHP的preg_match匹配字符串长度问题解决方法
2014/05/03 PHP
在Mac OS上自行编译安装Apache服务器和PHP解释器
2015/12/24 PHP
JQuery select标签操作代码段
2010/05/16 Javascript
基于jQuery的淡入淡出可自动切换的幻灯插件打包下载
2010/09/15 Javascript
js变量以及其作用域详解
2020/07/18 Javascript
javascript利用apply和arguments复用方法
2013/11/25 Javascript
js控制鼠标事件移动及移出效果显示
2014/10/19 Javascript
Javascript中的数据类型之旅
2015/10/18 Javascript
jQuery实现图片上传和裁剪插件Croppie
2015/11/29 Javascript
基于Three.js插件制作360度全景图
2016/11/29 Javascript
JS加密插件CryptoJS实现的Base64加密示例
2020/08/16 Javascript
微信开发之企业付款到银行卡接口开发的示例代码
2018/09/18 Javascript
Vuex实现数据增加和删除功能
2019/11/11 Javascript
Angular+Ionic使用queryParams实现跳转页传值的方法
2020/09/05 Javascript
python模块之StringIO使用示例
2015/04/08 Python
Python中处理字符串之islower()方法的使用简介
2015/05/19 Python
python实现稀疏矩阵示例代码
2017/06/09 Python
Python学生信息管理系统修改版
2018/03/13 Python
python实现ip代理池功能示例
2019/07/05 Python
把django中admin后台界面的英文修改为中文显示的方法
2019/07/26 Python
Django在admin后台集成TinyMCE富文本编辑器的例子
2019/08/09 Python
Python参数传递机制传值和传引用原理详解
2020/05/22 Python
CSS改变网页中鼠标选中文字背景颜色例子
2014/04/23 HTML / CSS
CSS3过渡transition效果实例介绍
2016/05/03 HTML / CSS
探究 canvas 绘图中撤销(undo)功能的实现方式详解
2018/05/17 HTML / CSS
Html5 audio标签样式的修改
2016/01/28 HTML / CSS
Paul’s Boutique官网:英国时尚手袋品牌
2018/03/31 全球购物
What is the purpose of Void class? Void类的作用是什么?
2016/10/31 面试题
中专毕业生的自荐书
2014/07/01 职场文书
党员个人剖析材料
2014/09/30 职场文书
先进典型发言材料
2014/12/30 职场文书
2015重阳节座谈会主持词
2015/07/30 职场文书
导游词之山西关帝庙
2019/11/01 职场文书
python爬虫框架feapde的使用简介
2021/04/20 Python
Win11黑色桌面背景怎么办?Win11黑色壁纸解决方法汇总
2022/04/05 数码科技