文章目录
文章目录功能大家应该很熟悉,主要用于长篇文章or教程内:用户可以根据自己需求,点击目录进行跳转。
常见的目录效果:
*目录的菜单项需要后端处理返回?
答案是No,后台同学觉得可以纯前端实现,所以就有了这篇文章,如何在不借助后台的情况下去生成目录呢?*
问题分析
一般需要生成目录的文章,标题都是需要标题和章节目录,所以必须要要有特定的标签修饰。举个例子:
分析源码:
- 网页应该是根据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;
效果图如下
还是蛮可的,哈哈😄