cheerio制作markDown索引目录

Vud3M8.png

制作目录索引这种东西当然是放在前端方便。选择放在后端一是为了了解 Node 后端生态,掌握更多后端技术;二是因为公司实行前后端分离的方式开发,睾贵的 JAVA 后端经常啥也不做处理就返回一个 row 数据(甚至有时时间戳都不处理),对此有些无语。

# 最终目标

VudNIs.png

  1. 点击索引单项跳转到相应标题
  2. 大号标题包含小号标题,小号标题向右缩进
  3. 滚动页面时自动切换索引项 active 状态

# 实现方法

# md 转化为 html

const markDown = require('marked')
markDown.setOptions({
  headerIds: false,
  highlight: function (code) {
    return require('highlight.js').highlightAuto(code).value
  }
})
let html = markDown(data.content)

# cheerio 生成索引

const cheerio = require('cheerio')

// decodeEntities防止中文转化为unicdoe
const $ = cheerio.load(html, { decodeEntities: false })

// 用hNum生成自定义id
let hArr = [],
  highestLvl,
  hNum = 0
$('h1, h2, h3, h4, h5, h6').each(function () {
  let id = `h${hNum}`
  hNum++
  $(this).attr('id', id)
  let lvl = $(this).get(0).tagName.substr(1)
  if (!highestLvl) highestLvl = lvl
  hArr.push({
    lvl: lvl - highestLvl + 1,
    content: $(this).html(),
    id: id
  })
})
Object.assign(data, {
  content: $.html,
  toc: hArr
})

# 前台展示

if data && data.toc
  ul#toc-wrapper.toc-wrapper-transform
    each item in data.toc
      // 利用lvl判断偏移量
      li(class='toc-item text-elli', style=`padding-left: ${item.lvl * 15}px`, id=item.id)
        a(href=`#${item.id}`, title=item.content).text-elli= item.content

# 页面滚动过自动切换 active

知道 getBoundingClientRect API 就好做了

function tocToggle() {
  if ($('.article-content').dom.length == 0) return
  let scrollArr = []
  document
    .querySelectorAll('.article-content h1, h2, h3, h4, h5, h6')
    .forEach((i) => {
      let elTop = Math.abs(i.getBoundingClientRect().top)
      scrollArr.push({
        el: i,
        top: elTop
      })
    })
  if (scrollArr.length == 0) return
  scrollArr = scrollArr.sort((a, b) => {
    return a.top - b.top
  })
  let activeId = $(scrollArr[0].el).attr('id')
  $(`#toc-wrapper #${activeId}`)
    .ac('toc-item-active')
    .siblings()
    .rc('toc-item-active')
}

$(window).on('scroll', () => {
  tocToggle()
})

tocToggle()

# Tips

# 锚点偏移

本网站的 header 是 fixed 在顶部的,锚点不进行偏移会盖住标题。偏移方法:

h1,
h2,
h3,
h4,
h5,
h6 {
  &:target {
    padding-top: 60px;
  }
}