提取H标签生成文章导航目录

1,072 阅读2分钟

记录实现为tinymce编辑器编辑的文章生成导航目录

要求:

提取页面中的H标签自动生成目录,并且能够实现锚点定位

思路:

  1. 获取文章中所有的h1~h4标签
  2. 比拟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');
          }
        });
      }
    },

说明:

粗糙实现功能, 还有待优化。