记录实现为tinymce编辑器编辑的文章生成导航目录
要求:
提取页面中的H标签自动生成目录,并且能够实现锚点定位
思路:
- 获取文章中所有的h1~h4标签
- 比拟h标签的数字,从以后h标签开始判断,如果前面的h标签数字比本人大则当做本人的子孙级,遇到h标签数字比本人小或和本人一样的则判断新层级执行
如果咱们获取到的h标签是这样的:
var hEles = [
'h4',
'h6',
'h3',
'h4',
'h4',
'h1',
'h2',
'h3',
'h3',
'h3',
'h3',
'h2',
'h3',
'h3'
];
则咱们首先须要将其转换成这样:
var arr2 = [
{hLevel: 4}, {hLevel: 6}, {hLevel: 3}, {hLevel: 4},
{hLevel: 4}, {hLevel: 1}, {hLevel: 2}, {hLevel: 3},
{hLevel: 3}, {hLevel: 3}, {hLevel: 3}, {hLevel: 2},
{hLevel: 3}, {hLevel: 3}
];
再转换成树状:
var res = [
{ hLevel: 4, level: 1, children: [ {hLevel: 6, level: 2} ] },
{ hLevel: 3,, level: 1, children: [ {hLevel: 4, level: 2}, {hLevel: 4, level: 2} ] },
{
hLevel: 1,
level: 1,
children: [
{
hLevel: 2,
level: 2
children: [ {hLevel: 3, level: 3}, {hLevel: 3, level: 3}, {hLevel: 3, level: 3}, {hLevel: 3, level: 3} ]
},
{
hLevel: 2,
level: 2,
children: [ {hLevel: 3, level: 3}, {hLevel: 3, level: 3} ]
}
]
}
];
代码实现
// 获取需要的H标签
function getTocH(dom) {
return dom.querySelectorAll('h1, h2, h3, h4');
}// 为每个H标签增加id
function addHLavelId(domList) {
for (let i = 0; i < domList.length; i++) {
domList[i].setAttribute('id', `anchor_${i}`);
}
}// 转换为树形结构
function toTree(flatArr) { var tree = [];
var level = 1;
var copyArr = [].slice.call(flatArr).map(function (item) {
return { ...item, hLevel: item.nodeName.slice(1), textContent: item.textContent, id: item.id };
});
// 依据指定级别查找该级别的子孙级,并删除掉曾经查找到的子孙级
var getChildrenByLevel = function (currentLevelItem, arr) {
if (!currentLevelItem) {
return;
}
// 将level值转成正数,再进行比拟
var minusCurrentLevel = -currentLevelItem.hLevel;
var children = [];
for (var i = 0, len = arr.length; i < len; i++) {
var levelItem = arr[i];
if (-levelItem.hLevel < minusCurrentLevel) {
children.push(levelItem);
} else {
// 只找最近那些子孙级
break;
}
}
// 从数组中删除曾经找到的那些子孙级,免得影响到其余子孙级的查找
if (children.length > 0) {
arr.splice(0, children.length);
}
return children;
};
var getTree = function (result, arr, level) {
// 首先将数组第一位移除掉,并增加到后果集中
var currentItem = arr.shift();
if (currentItem) {
currentItem.level = level;
result.push(currentItem);
}
while (arr.length > 0) {
if (!currentItem) {
return;
}
// 依据以后级别获取它的子孙级
var children = getChildrenByLevel(currentItem, arr);
// 如果以后级别没有子孙级则开始下一个
if (children.length == 0) {
currentItem = arr.shift();
if (currentItem) {
currentItem.level = level;
result.push(currentItem);
}
continue;
}
currentItem.children = [];
// 查找到的子孙级持续查找子孙级
getTree(currentItem.children, children, level + 1);
}
};
getTree(tree, copyArr, level);
return tree;
}
// 依据树状构造数据生成章节目录dom树
function getChapterDomTree(chapterTreeData, parentNode) {
if (!parentNode) {
parentNode = createNodeByHtmlStr('<div class="toc-list"></div>')[0];
}
Array.from(chapterTreeData).map(chapterItem => {
var itemDom = createNodeByHtmlStr(
'<div class="toc-list-Item"><a class="toc-level toc-level-' +
chapterItem.level +
'" data-href="#' +
chapterItem.id +
'">' +
chapterItem.textContent +
'</a></>'
)[0];
parentNode.appendChild(itemDom);
if (chapterItem.children) {
var catalogList = createNodeByHtmlStr('<div class="toc-child-list"></div>')[0];
itemDom.appendChild(catalogList);
getChapterDomTree(chapterItem.children, catalogList);
}
});
return parentNode;
}
// 依据html字符串生成dom元素
function createNodeByHtmlStr(htmlStr) {
var div = document.createElement('div');
div.innerHTML = htmlStr;
var children = div.children;
div = null;
return children;
}
/// 生成导航数据
function createToc(dom) {
let TocList = getTocH(dom);
addHLavelId(dom);
let TocTree = toTree(TocList);
let TocDom = getChapterDomTree(TocTree);
return TocDom;
}
j
将生成的数据挂载到指定的位置
document.getElementById('toc').appendChild(nv);
实现锚点功能
点击目录大纲
addClickEvent() {
let domA = document.querySelectorAll('#toc a');
for (let i = 0; i < domA.length; i++) {
domA[i].addEventListener('click', function (e) {
// 增加选中样式之前删除之前的选中样式
removeActive()
e.target.classList.add('active');
let anchorId = e.target.dataset.href;
// 滚动文档展示模块到滚动到anchorId位置
goTarget(anchorId)
});
}
},
removeActive() {
//删除选中样式
let rDom = document.querySelectorAll('#toc a.active');
if (rDom && rDom.length) {
rDom[0].classList.remove('active');
}
},
// 滚动文档
goTarget(id) {
//获取当前被定位元素
let targerDom = document.getElementById('htmlPreview').querySelector(id);
//获取目标元素距离视窗左上角的高度距离,可以是负数
let targetHeight = targerDom.offsetTop;
let height = targetHeight - 50;
//将视窗固定到所需要的位置
document.getElementById('wikiDetail').scrollTo(0, height);
},
滚动文章定位目录
//监听滚动事件
scrollEvent(){
let targerDom = document.getElementById('htmlPreview');
targerDom.addEventListener('scroll', event => {
// 判断激活的目录
isActive(event.target.scrollTop);
})
}
// 判断激活的目录
isActive(scrollTop) {
let domA = document.querySelectorAll('#toc a');
for (let i = 0; i < domA.length; i++) {
// 判断当前元素是否为
let anchorId = domA[i].dataset.href;
let targerDom = document.getElementById('htmlPreview').querySelector(anchorId); //获取目标元素距离视窗左上角的高度距离,可以是负数
let targetHeight = targerDom.offsetTop;
let Distance = targetHeight - scrollTop;
// Distance 在某个范围就激活当前目录
if (0 < Distance && Distance < 130) {
removeActive();
domA[i].classList.add('active');
}
});
}
},
说明:
粗糙实现功能, 还有待优化。