1 tocbot的介绍
使用tocbot可以把HTML代码进行解析,生成一个目录添加到一个容器中,并且在每次滚动经过标题时会更新位置,对于文档网站或Markdown页面很有帮助,tocbot使浏览更加容易。
tocbot使用原生DOM方法,没有依赖关系,可以在启用JavaScript的任何浏览器中运行。
2 tocbot源码解读
源代码地址:https://github.com/tscanlin/tocbot
2.1 代码结构
├── package.json
├── pages
│ ├── _config.js // 网页中所需数据
│ ├── index.js //入口文件,Next.js是围绕page的概念构建的,page是在pages目录中的js/jsx/ts/tsx文件导出的React组件。
├── server.js ////使用express框架,Next.js一个轻量级的React服务端渲染应用框架。
├── src
│ ├── components
│ │ └── Template ////主要结构文件,针对于在浏览器中展示的结构和样式
│ ├── js
│ │ ├── build-html.js // 将数组转为节点更新渲染到目录容器中
│ │ ├── default-options.js //默认参数数据
│ │ ├── index.js ////主要逻辑文件,包括tocbot的初始化,销毁和刷新
│ │ ├── parse-content.js // 将节点转为嵌套数组
│ │ └── scroll-smooth //滚动功能初始化
2.2 源码方法解析
源码方法解析主要从提取标题节点,将节点注入到目录容器以及实现滚动三部分讲明。
tocbot.init()方法执行后,首先将默认属性对象和传入的参数对象的自身属性合并成一个对象返回保存在options属性中。主要参数包括:tocSelector目录容器,contentSelector抓取标题的文章容器,headingSelector抓取的标题元素,ignoreSelector忽略的元素。
2.2.1 提取解析标题节点
如果tocbot对象已经存在,就先销毁。然后执行selectHeadings()方法,该方法传入两个参数,分别是文章容器contentSelector和需要解析的标题headingSelector。排除忽略的元素,使用querySelectorAll在文章容器中提取需要解析的标题节点,保存在headingsArray变量中。
代码如下:
function selectHeadings (contentSelector, headingSelector) {
var selectors = headingSelector
if (options.ignoreSelector) {
selectors = headingSelector.split(',')
.map(function mapSelectors (selector) {
return selector.trim() + ':not(' + options.ignoreSelector + ')'
})
}
try {
return document.querySelector(contentSelector)
.querySelectorAll(selectors)
} catch (e) {
console.warn('Element not found: ' + contentSelector); // eslint-disable-line
return null
}
}
2.2.2 节点注入到目录容器
首先将获取到的需要解析的标题节点转为嵌套数组。接下来执行nestHeadingsArray()函数,参数为获取到的需要解析的标题节点数据headingsArray,在该方法中,每个节点都调用一次getHeadingObject()函数,参数为当前节点,将节点的id,名称和内容以及几级标题,子节点等信息转为一个对象返回保存在变量currentHeading中。然后执行addNode()函数,参数为当前节点对象currentHeading和上次结果数组nest,在数组nest中找到当前节点对象对应的位置插入,最后得到一个嵌套数组保存在nestedHeadings变量中。
代码如下:
function nestHeadingsArray (headingsArray) {
return reduce.call(headingsArray, function reducer (prev, curr) {
var currentHeading = getHeadingObject(curr)
if (currentHeading) {
addNode(currentHeading, prev.nest)
}
return prev
}, {
nest: []
})
}
接下来,将嵌套数组转为DOM节点,插入到构建目录的容器中。
接着,执行buildHtml文件的render()函数,参数为构建目录的容器tocSelector和获取的嵌套数组nestedHeadings,调用createList()函数,根据options.orderedList变量的值创建一个ul或ol元素作为父容器,给这个元素添加样式。遍历嵌套数组,执行createEl()函数分别给对应元素添加子元素,然后把父容器ul或ol元素添加到tocSelector构建目录的容器中。
代码实现如下:
function render (selector, data) {
var collapsed = false
var container = createList(collapsed)
data.forEach(function (d) {
createEl(d, container)
})
var parent = document.querySelector(selector)
// Return if no parent is found.
if (parent === null) {
return
}
// Remove existing child if it exists.
if (parent.firstChild) {
parent.removeChild(parent.firstChild)
}
// Just return the parent and don't append the list if no links are found.
if (data.length === 0) {
return parent
}
// Append the Elements that have been created
return parent.appendChild(container)
}
2.2.3 滚动功能
通过点击目录滚动到文章相应位置。
根据scrollSmooth属性,如果启用滚动功能,就执行initSmoothScrolling()函数初始化滚动功能,添加onClick点击监听事件,当鼠标点击后,执行回调函数中jump()函数,参数为hash值和由滚动持续时间、偏移量和回调函数组成的对象。根据当前高亮的id元素和点击的id元素计算滚动的高度,外加滑动的持续时间和滑动的偏移量,执行requestAnimationFrame()函数,告诉浏览器希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画,滚动到点击元素指定位置,点击的元素获得焦点。
代码实现如下:
function jump (target, options) {
var start = window.pageYOffset
var opt = {
duration: options.duration,
offset: options.offset || 0,
callback: options.callback,
easing: options.easing || easeInOutQuad
}
var tgt = document.querySelector('[id="' + decodeURI(target).split('#').join('') + '"]')
var distance = typeof target === 'string'
? opt.offset + (
target
? (tgt && tgt.getBoundingClientRect().top) || 0 // handle non-existent links better.
: -(document.documentElement.scrollTop || document.body.scrollTop))
: target
var duration = typeof opt.duration === 'function'
? opt.duration(distance)
: opt.duration
var timeStart
var timeElapsed
requestAnimationFrame(function (time) { timeStart = time; loop(time) })
function loop (time) {
timeElapsed = time - timeStart
window.scrollTo(0, opt.easing(timeElapsed, start, distance, duration))
if (timeElapsed < duration) { requestAnimationFrame(loop) } else { end() }
}
function end () {
window.scrollTo(0, start + distance)
if (typeof opt.callback === 'function') { opt.callback() }
}
}
点击目录,页面内容定位到对应位置,同时,页面滚动到相关节点时,目录中对应的节点样式也应该更新。
然后给滚动容器绑定滚动和重新设置大小的监听事件,回调函数使用节流函数,每隔50毫秒执行一次下面的函数,该函数中调用updateToc()函数,根据当前滚动的高度添加删除样式去更新目录的高亮和折叠目录分组。执行updateTocScroll()函数,如果整个目录元素高度大于容器的高度,就让目录元素的垂直滚动的像素等于当前活跃的元素的垂直偏移量。绑定点击监听事件,点击目录时,取消高亮,更新目录,重新高亮。
this._scrollListener = throttle(function (e) {
buildHtml.updateToc(headingsArray);
!options.disableTocScrollSync && updateTocScroll(options);
var isTop =
e &&
e.target &&
e.target.scrollingElement &&
e.target.scrollingElement.scrollTop === 0;
if ((e && (e.eventPhase === 0 || e.currentTarget === null)) || isTop) {
buildHtml.updateToc(headingsArray);
if (options.scrollEndCallback) {
options.scrollEndCallback(e);
}
}
}, options.throttleTimeout);
当Tocbot组件销毁之前,清空容器元素的HTML内容,移除监听事件。
3 tocbot的使用
使用npm安装
npm i tocbot -S
在scss文件中,从node_modules中导入样式,用于展开&折叠分组的一些样式。
@import 'tocbot/src/scss/tocbot.scss';
@import 'tocbot/src/scss/_tocbot-default-theme.scss';
在js文件中引入tocbot,调用初始化方法,传入一个对象参数.
import tocbot from 'tocbot'//初始化时
tocbot.init({
tocSelector: '.js-toc', //构建目录的容器
contentSelector: '#js-toc-content', //文章目录
headingSelector: 'h1, h2, h3', //需要解析的标题
scrollContainer: null,
})
tocbot.refresh() //如果文档修改或重新生成时,触发刷新时调用
tocbot.destory() //删除tocbot,移除监听
4 总结
tocbot部分参数的用法:
tocbot中有一个fixedSidebarOffset属性,默认设置为auto,实现滚动跟随功能,在目录不可见时,tocbot会为元素添加is-position-fixed类,目录变成fixed,跟着屏幕进行滚动。
tocbot中scrollSmoothOffset属性对应scroll-smooth的offset参数,实现在跳转同时进行相应的偏移。
tocbot插件主要通过标签id属性实现跳转,如果标签中没有id属性或者id中包含中文字符,无法跳转。
以上内容是文章的全部内容,希望本文能对您有所帮助,谢谢。