likes
comments
collection
share

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

作者站长头像
站长
· 阅读数 20

1. 引言

起因

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

2. 插件设计

插件设计思路

获取每一篇文章的内容,遍历标题,生成对应的结构,最后将dom添加到页面右侧边栏。

插件功能

1. 生成侧边栏 2. 浏览到文章后面部分时,定位在页面上 3. 根据文章的标题,生成的目录会有对应的缩进 4. 当前浏览的标题会进行高亮显示 5. 点击标题跳转到对应的文章部分

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

3.插件开发过程

使用的技术和工具

JavaScript

关键的开发步骤和代码实现

获取文章主题以及标题节点

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

可以看出标题与文章的结构关系是:所有标题都是文章的子节点,区别只有为H1~H5。所以代码只需要在类选择器crayons-article__bodychildNodes中通过正则进行筛选。得到所有的H1~H5节点。 代码如下:

const articleBody = document.querySelector('.crayons-article__body');
const regex = /\bH[1-5]\b/g;
const headings = [...articleBody.childNodes].filter((node) => 
    node.nodeType !== 3 && node.tagName && regex.test(node.tagName)
);

插件侧边栏容器

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

获取到侧边栏要append的父节点crayons-layout__sidebar-right,为侧边栏创建一个容器以及为其设置一定的样式,样式主要是使用了网站自带样式类型进行复用

  const sidebarRight = document.querySelector('.crayons-layout__sidebar-right');
  const tocContainer = document.createElement('ul');
  const divContainer = document.createElement('div');
  divContainer.className = 'crayons-article-sticky crayons-card crayons-card--secondary crayons-sponsorship billboard mt-4';
  divContainer.appendChild(tocContainer);
  tocContainer.id = 'toc-container';
  tocContainer.style.paddingLeft = '20px';

侧边栏加入内容,进行层级处理

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

  1. 首先在上面已经拿到了了文章中的所有标题节点,按照顺序,创建新的li节点,新创建一个数组hStack,记录当前的处理的标题level,当数组hStack空或者新的level较小(也就是出现了H3标签后面紧跟了H4~H5标签)时进行push操作。而在有了更高层级的level标签时,进行pop操作。最后的hStack长度就是当前标题需要进行缩进的格数。
  2. 在本段代码中,还顺便绑完成了点击标题进行跳转到对应目录的页面位置。
  const hStack = [];
  const linkTargets = headings.map((heading, index) => {
    const hItem = document.createElement('li');
    const level = Number(heading.tagName.slice(1));

    hItem.textContent = heading.textContent;
    hItem.style.color = 'var(--body-color)';
    hItem.style.cursor = 'pointer';
    hItem.onclick = () => heading.scrollIntoView({ behavior: "smooth" });

    tocContainer.appendChild(hItem);

    if (!hStack.length || hStack[hStack.length - 1] < level) {
      hStack.push(level);
    } else if (hStack[hStack.length - 1] > level) {
      hStack.pop();
    }
    hItem.style.marginLeft = (hStack.length) * 15 + 'px';

    return hItem;
  });

滑动页面侧边栏对应高亮

✨为了吃🍉,我给DEV写了一个文章侧边栏插件

  1. 订阅scroll事件,之前函数执行的结果会将处理后的linkTargets(侧边栏的dom数组)以及headings(文章的标题dom数组)保存为全局变量。
  2. 在订阅事件中,判断页面滚动高度和哪个文章标题的距离顶部高度相符合,确定数组序号,再为侧标栏中对应序号设置active类名。
  3. 为页面添加active对应的样式style标签
window.addEventListener('scroll', () => {
  if (!tocData) return;

  const { headings, linkTargets } = tocData;
  const scrollPos =
    window.pageYOffset ||
    document.documentElement.scrollTop ||
    document.body.scrollTop ||
    0;

  const currIndex = headings.findIndex(h => h.offsetTop + 20 >= scrollPos);

  linkTargets.forEach((link, index) => link.classList.toggle('active', index === currIndex));
});
const styleElement = document.createElement('style');
styleElement.textContent = `
  #toc-container .active {
    color: var(--link-branded-color)!important;
    font-weight: bold;
  }
`;
document.head.appendChild(styleElement);

监听文章变化,重新生成目录

因为网站使用的是服务端渲染,只有地址栏地址会发生变化。所以让gpt帮我生成了一个方案进行处理,然后在其中出现执行生成函数,并更新全局变量linkTargets(侧边栏的dom数组) headings(文章的标题dom数组)

let oldHref = document.location.href;
const observer = new MutationObserver(() => {
  if (oldHref != document.location.href) {
    oldHref = document.location.href;
    tocData = genTOC();
  }
});

observer.observe(document.body, { childList: true, subtree: true });

插件地址✨

最后我还扔给了gpt帮我优化代码

✨为了吃🍉,我给DEV写了一个文章侧边栏插件 greasyfork.org/zh-CN/scrip…