前端--文章根据页面标签生成目录

1,769 阅读1分钟

文章目录

文章目录功能大家应该很熟悉,主要用于长篇文章or教程内:用户可以根据自己需求,点击目录进行跳转。

常见的目录效果:

image.png

*目录的菜单项需要后端处理返回?

答案是No,后台同学觉得可以纯前端实现,所以就有了这篇文章,如何在不借助后台的情况下去生成目录呢?*

问题分析

一般需要生成目录的文章,标题都是需要标题和章节目录,所以必须要要有特定的标签修饰。举个例子:

image.png

分析源码:

  • 网页应该是根据Markdown生成的
  • 文章分目录,使用html的<h1><h2><h3>标签,进行分层。
  • 每个标题标签,自带ID,可以使用“#”进行文章定位

综上,就很清晰了:

  • 提取内容部分的<h1>~<h3>标签(三层的目录……不多不少,嘿嘿),生成tree结构
  • 提取/放置标签ID,作为目录索引,便于目录功能的文章定位

代码实现

import React, { useState, useEffect } from 'react';
import { Anchor, Affix } from 'antd';
import style from './index.module.less';

const { Link } = Anchor;
interface IProps {
  content: string;
  needCateLog?: boolean;
}

function TineViewer(props: IProps) {
  const { content, needCateLog = false } = props;
  const [visible, setVisible] = React.useState(false);
  const [images, setImages] = useState<any>('');
  const [titles, setTitles] = useState<any>([]);
  useEffect(() => {
    initCate();
  }, [content]);
  function initCate() {
    const eles = ['h1','h2','h3'];
    // 获取文章里 h1 到 h3 的元素
    const doms: any = document?.querySelector('.editorContentWrapper')?.querySelectorAll(eles.toString()) || [];
    const titlesList: any = [];
    if (!doms || !doms.length) {
      return;
    }
    // 目录的下标
    let index = 0;
    for (const h of doms) {
      const tag = h.nodeName.toLowerCase();
      if (!eles.includes(tag)) {
        continue;
      }

      // 生成每个目录的id,绑定在 h 标签上
      const id = `catalog_${++index}`;
      const text = h.textContent.replace(/<\/?[^>]+>/g, '');
      h.setAttribute('id', id);
      titlesList.push({
        id,
        title: text,
        level: Number(h.nodeName.substring(1, 2)),
        nodeName: h.nodeName,
      });
    }
    setTitles(titlesList);
  }
  return (
    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
      <div
        className="previewRichText ql-snow"
      >
        <div
          className="editorContentWrapper ql-editor mce-content-body"
          // eslint-disable-next-line
        dangerouslySetInnerHTML={{
            __html: props?.content || '',
          }}
        />
      </div>
      {needCateLog && (
        <div className={style.cateLogWrap}>
          <Affix offsetTop={60}>
            <div>
              <div className={style.title} key="mulu">
                目录
              </div>
              <Anchor offsetTop={80}>
                {titles.map((item: any) => (
                  <Link key={item.id} title={item.title} href={`#${item.id}`} />
                ))}
              </Anchor>
            </div>
          </Affix>
        </div>
      )}
    </div>
  );
}

export default TineViewer;

效果图如下

image.png 还是蛮可的,哈哈😄