js实现文章目录生成以及目录跟随内容滚动变化

Generate Article Catalogs and Switch Catalog Following Article's Scroll Using JavaScript

前两天,在我的第一篇博客文章程序员如何搭建自己的个人博客中,我简单实现了文章目录的生成,并且能够点击目录自动跳转到对应的章节。但是如果阅读到了文章的其他章节,目录就不能实现自动切换,这对于阅读者来说就不太方便,所以今天我就用JavaScript实现了文章目录跟随内容滚动自动切换的功能。本文我就把用js实现文章目录生成以及目录跟随内容滚动变化的方法记录一下。

目标

首先,需要明确本文要实现的功能如下:

  1. 根据文章内容自动生成文章目录
  2. 点击目录跳转到对应章节
  3. 滚动阅读文章时,目录能自动切换到当前阅读的章节
  4. 目录跟随文章内容滚动

下面我就以文章程序员如何搭建自己的个人博客及其目录为例,介绍一下我的具体实现思路和办法。

根据文章内容自动生成文章目录

实现这一功能的前提是文章内容有清晰的框架结构,即文章必须定义有层次合理的标题,这样才能通过js从中提取出目录结构。HTML标准为我们提供了最简单的定义标题的方式,那就是使用 h1 ~ h6 标签。

有了定义好的标题,我们就可以用js提取出文章的标题:

for (let heading of $('h1,h2,h3,h4,h5,h6')) { const headingLevel = heading.tagName.toLowerCase(); const $heading = $(heading); const headingName = $heading.text().trim(); console.log(headingLevel, headingName); }

控制台打印出的结果如下:

提取出的文章标题

其中 headingLevel 表示标题级别,headingName 就是标题的名称。将提取出来的标题插入到用于展示文章目录的标签中,并在 .css 文件中调整好缩进样式,就实现了文章目录的生成:

$('#catalogs').append(`<div class="catalog catalog-${headingLevel}">${headingName}</div>`); .catalog-h2 { margin-left: 1em; } .catalog-h3 { margin-left: 2em; } .catalog-h4 { margin-left: 3em; } .catalog-h5 { margin-left: 4em; } .catalog-h6 { margin-left: 5em; }

其中,带有 catalog 类名的标签就表示一条目录。最终根据文章内容自动生成的文章目录如下:

生成的文章目录

点击目录跳转到对应章节

实现点击跳转最简单的方式就是在文章标题位置添加锚点,添加锚点有两种方式:

  1. h1 ~ h6 标签上添加 id 属性
  2. h1 ~ h6 标签内部添加一个带有 name 属性的 a 标签

由于方法1比较简便,我们直接在文章的各级标题上添加好 id 属性,然后修改根据文章内容自动生成文章目录中插入目录的代码:

const anchorName = $heading.attr('id'); $('#catalogs').append( ` <div class="catalog catalog-${headingLevel}" name="${anchorName}"> <a href="#${anchorName}">${headingName}</a> </div> ` );

上面代码从标题中取出 id 属性作为锚点名,然后在每一条目录中都加入了一个链接到对应标题的 a 标签,这样,点击某一条目录就能通过锚点直接跳转到文章对应的章节。

目录自动切换到当前阅读的章节

为了在阅读文章时,让目录也随着当前阅读的位置自动切换,我们首先需要获取当前正在阅读的文章位置。可以以标题作为标志,通过js来判断当前页面内容位于哪一个标题之下,首先定义一个 catalogTrack 方法:

const catalogTrack = () => { let $currentHeading = $('h1'); for (let heading of $('h1,h2,h3,h4,h5,h6')) { const $heading = $(heading); if ($heading.offset().top - $(document).scrollTop() > 20) { break; } $currentHeading = $heading; } };

假设我们以浏览器窗口顶部下方20px处的水平线为基准线,高于该基准线的章节表示已经被阅读完毕或者正在被阅读,低于该基准线的章节表示即将会被阅读,所以上面代码第5行的判断条件值为20。

该方法会遍历所有的文章标题,然后在第5行判断它们与浏览器窗口顶部的相对位置,直到有一个标题与浏览器窗口顶部的高度差大于20px,则跳出循环,那么此时的 $currentHeading 所表示的章节就是正在被阅读的章节。接下来,我们只需要在 catalogTrack 方法中找到目录中与该章节对应的条目,将其标注为正在被阅读的章节即可:

const anchorName = $currentHeading.attr('id'); const $catalog = $(`.catalog[name="${anchorName}"]`); if (!$catalog.hasClass('catalog-active')) { $('.catalog-active').removeClass('catalog-active'); $catalog.addClass('catalog-active'); }

第3到6行会为当前正在被阅读的章节目录添加一个类名 catalog-active,通过该类名为目录赋予一定的样式,就完成了目录随当前阅读位置的切换,下面是一个展示当前效果的动图:

目录自动切换到当前阅读的章节

目录跟随文章内容滚动

实现了目录自动切换以后,还可能存在一个问题,就是如果目录的条数过多,超出目录区域,那么当前添加有 catalog-active 类名的条目就有可能会被部分或者完全遮挡,例如下面的情况:

当前目录被遮挡

为了让用户能随时看到当前正在被阅读的章节目录,我们就需要用js控制目录区域的滚动。只需要修改 catalogTrack 方法,在目录切换之后加入以下代码:

if ($catalog.length > 0) { $('#catalogs').scrollTop($catalog[0].offsetTop - 50); } else { $('#catalogs').scrollTop(0); }

上面代码会保证当前正在被阅读的章节目录距离目录区域顶端的高度为50px(即第2行中数值50的意义)。最终实现的效果如下:

目录跟随文章内容滚动

总结

本文记录了我在博客文章目录生成及控制过程中的思路和方法,在实际应用中,针对不同情形的需求可能还会有其他解决方法,本文的方法可以供大家参考。希望大家有好的意见和建议也可以在下方评论区域进行讨论,感谢大家的阅读~

文章评论
${fromAuthor ? '郄正元' : '游客'} 作者 ${gmtCreate}
${content}
${subList.length}
发表评论
${commentToArticle ? '' : parentContent}
字数:0/${maxCommentLength}
该邮箱地址仅用于接收其他用户的回复提醒,不会泄露