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.cvtooltip.js 基于jquery的气泡提示插件
Nov 19 Javascript
一个非常全面的javascript URL解析函数和分段URL解析方法
Apr 12 Javascript
jQuery的css()方法用法实例
Dec 24 Javascript
JavaScript匿名函数用法分析
Feb 13 Javascript
js实现两点之间画线的方法
May 12 Javascript
jQuery动态添加可拖动元素完整实例(附demo源码下载)
Jun 21 Javascript
深入学习jQuery中的data()
Dec 22 Javascript
Vue2.0结合webuploader实现文件分片上传功能
Mar 09 Javascript
垃圾回收器的相关知识点总结
May 13 Javascript
Vue项目查看当前使用的elementUI版本的方法
Sep 27 Javascript
小程序websocket心跳库(websocket-heartbeat-miniprogram)
Feb 23 Javascript
微信小程序实现上传照片代码实例解析
Aug 04 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中用数组的方法设置cookies
2011/04/21 PHP
浅析php中jsonp的跨域实例
2013/06/21 PHP
php中删除、清空session的方式总结
2015/10/09 PHP
php 计算两个时间相差的天数、小时数、分钟数、秒数详解及实例代码
2016/11/09 PHP
php 下 html5 XHR2 + FormData + File API 上传文件操作实例分析
2020/02/28 PHP
jQuery下通过replace字符串替换实现大小图片切换
2012/05/22 Javascript
JS判断不同分辨率调用不同的CSS样式文件实现思路及测试代码
2013/01/23 Javascript
jQuery之自动完成组件的深入解析
2013/06/19 Javascript
JS获取鼠标坐标的实例方法
2013/07/18 Javascript
jquery删除提示框弹出是否删除对话框
2014/01/07 Javascript
jQuery基于当前元素进行下一步的遍历
2014/05/20 Javascript
教你如何自定义百度分享插件以及bshare分享插件的分享按钮
2014/06/20 Javascript
深入理解javascript构造函数和原型对象
2014/09/23 Javascript
js多个物体运动功能实例分析
2016/12/20 Javascript
使用react-router4.0实现重定向和404功能的方法
2017/08/28 Javascript
angular中不同的组件间传值与通信的方法
2017/11/04 Javascript
Vue+Element实现动态生成新表单并添加验证功能
2019/05/23 Javascript
一个手写的vue放大镜效果
2019/08/09 Javascript
JS可断点续传文件上传实现代码解析
2020/07/30 Javascript
[04:54]DOTA2 2017国际邀请赛:上届冠军WINGS采访短片
2017/08/09 DOTA
python实现根据主机名字获得所有ip地址的方法
2015/06/28 Python
Python时间戳使用和相互转换详解
2017/12/11 Python
人生苦短我用python python如何快速入门?
2018/03/12 Python
Django组件之cookie与session的使用方法
2019/01/10 Python
python3利用Axes3D库画3D模型图
2020/03/25 Python
Scrapy模拟登录赶集网的实现代码
2020/07/07 Python
Python3爬虫关于识别点触点选验证码的实例讲解
2020/07/30 Python
音乐表演专业毕业生求职信
2013/10/14 职场文书
通信工程毕业生自荐信
2013/11/01 职场文书
黄河的主人教学反思
2014/02/07 职场文书
消防先进事迹材料
2014/02/10 职场文书
2014迎新年晚会策划方案
2014/02/23 职场文书
聘任书模板
2014/03/29 职场文书
依法行政工作汇报材料
2014/10/28 职场文书
指导老师鉴定意见
2015/06/05 职场文书
JavaScript实现两个数组的交集
2022/03/25 Javascript