在antd里面渲染MarkDown并且自定义一个锚点目录TOC(重点解决导航目录不跟随文档滚动的问题)

一、整体思路

由于有很多很长的文档需要渲染,我觉得用MarkDown的方式会比较适合管理,所以这两天测试了一下在antd里面集成MarkDown的渲染模块。
总体思路参考:
https://blog.csdn.net/Sakuraaaa_/article/details/128400497
感恩大佬的倾情付出。

    1. 使用react-markdown解析MarkDown
    1. 使用antd的Anchor锚点组件做一个类TOC的跳转目录

二、不说废话

1.完整代码

import React, {useState, useEffect, useRef} from 'react';
import Markdown from 'react-markdown';
import {getMarkDown} from "@/services/ant-design-pro/api";
import 'github-markdown-css';
import {Collapse, Anchor, BackTop, Col, Row} from "antd";
const { Link } = Anchor;
const { Panel } = Collapse;
import styles from './style.less';

const App = ({markdownFileName}) => {

  const [mdContent, setMdContent] = useState('')
  const [at, setAt] = useState('')

  useEffect(() => {
    let ret = getMarkDown(markdownFileName)
    ret.then((res)=>{
      setMdContent(res)
      addAnchor()
    })
  }, []);


  const addAnchor = () => {
    const ele = document.getElementsByClassName('markdown-body')[0];
    let eid = 0;
    let ttitles = [];
    for (const e of ele.childNodes) {
      // if (e.nodeName === 'H1' || e.nodeName === 'H2' || e.nodeName === 'H3' || e.nodeName === 'H4' || e.nodeName === 'H5' || e.nodeName === 'H6') {
      if (e.nodeName === 'H2') {
        let a = document.createElement('a');
        a.setAttribute('id',  'h2_' + eid);
        a.setAttribute('class', 'anchor-title');
        a.setAttribute('href', '#' + eid);
        a.innerText = ' '
        let title = {
          type: e.nodeName,
          id: eid,
          name: e.innerText
        };
        ttitles.push(title);
        e.appendChild(a);
        eid++;
      }
    }
    setAt((
      <Anchor
        onClick={handleClickFun}
        getContainer={()=>ele}
      >
        {
          ttitles.map(t => (
            <Link href={'#h2_' + t.id} title={t.name}/>
          ))
        }
      </Anchor>
    ))

  }


  const handleClickFun = (e, link) => {
    console.log('click')
    e.preventDefault();
    if (link.href) {
      // 找到锚点对应得的节点
      let element = document.getElementById(link.href);
      // 如果对应id的锚点存在,就跳滚动到锚点顶部
      element && element.scrollIntoView({ block: 'start', behavior: 'auto' });
    }
  }


  return (
      <div className="scrollable-container">
        <div
            className={styles.markdownnav}
        >
          <Collapse defaultActiveKey={['1']} ghost>
            <Panel header="章节目录" key="1">
            {at}
          </Panel>
        </Collapse>
        </div>

          <Markdown
            className='markdown-body'
          >{mdContent}
          </Markdown>
        <BackTop/>
      </div>
  )
}

export default App

效果,由于手头上没有长的md,简单演示下。
在这里插入图片描述
代码很简单,主要和大家说说我遇到的几个问题。

二、难点?或者说要点

1.MarkDown自定义样式

import 'github-markdown-css';

引入这个风格后确实文档的样式有所美观,
但我还需要根据自己的要求修改几个风格,在引入的less中用global参数覆盖上面的样式就可以。原理可以看看我自定义dot那篇。

:global {
  .markdown-body h1 {
    //margin: .67em 0;
    font-weight: var(--base-text-weight-semibold, 600);
    padding-bottom: .3em;
    font-size: 2em;
    border-bottom: 1px solid var(--color-border-muted);
    margin: 0 auto;
    text-align: center;
  }

  .markdown-body p{
    text-indent: 2em; /*首行缩进*/
  }
}

2.导航目录生成

虽然我接触react时间也不短了,但是确实也没有精细的研究过react的渲染过程中几个回调的顺序问题。尤其是函数组件的资料更难找。
你看我这里的jsx里面,并没有直接把Anchor组件写在这里,而是写在了MarkDown渲染完成后的回调中。

setAt((
      <Anchor
        onClick={handleClickFun}
        getContainer={()=>ele}
      >
        {
          ttitles.map(t => (
            <Link href={'#h2_' + t.id} title={t.name}/>
          ))
        }
      </Anchor>
    ))

原因就是我这里需要设置getContainer的值,默认是获取window,那window铁定是不滚动的,还我们需要把MarkDown的elment给他挂上去。如果直接写需要用ref的方式获取,但是MarkDown组件没办法获取他的ref,我不知道怎么解决这个问题,所以讨巧的再useEffect里面来做目录组件的插入,因为这里Dom的渲染已经完成,我们可以通过document.getElementsByClassName来获取MarkDown的element。

3.导航目录随文档滚动问题

根据上面的S大佬的代码写,无论我怎么调整getContainer的容器都无法跟随文档滚动。
所实话这个也不是我第一次吐槽antd了,这个文档真的是简陋,不够这次我痛定思痛。
根据https://juejin.cn/post/7158430758070140942这位大佬的思路,测地落实了一下用webstrom的断点来调试源码的方法。终于搞清楚为啥没办法跟随文档滚动。

我们需要先明白一个点:Anchor的代码里面是怎么实现滚动监听的呢?
实现还挺粗暴的,对你传入的getContainer的容器进行滚动监听,当出现滚动事件的时候,检查容器中的所有跳转链接和顶部的距离,来设置激活的目录。

问题出在S大佬的代码中插入a链接的id上。
在这里插入图片描述
可以看到S大佬这里给id的前面加了个#,这个#在点击跳转的时候不会有问题,但是在滚动的时候就会出现问题。原因是这个地方进行了一个正则的过滤
在这里插入图片描述
这个正则主要的工作是过滤掉前面的#号
在这里插入图片描述
然而根据S大佬的id配置的是#开头的。所以就导致这里的id没办法找到对应的a标签,所以就没办法监听到他的滚动位置。
解决方案也很简单。

  • 1.在设置跳转标签,也就是插入a标签的地方。a标签的id不能带前面的#
  • 2.在Anchor的Link中需要带#

这样设置完就可以看到目录跟随文章滚动的效果了。

上面我说了我要吐槽antd的文档,因为这么重要的设置他甚至都没有给一个示例代码,一切都需要靠自己分析自己猜,很好奇这个东西他们自己到底用不用?还是和语雀一样外部一套内部一套,外部这个就做着玩的吧。

PS

1.webstrom 用Chrome 调试时遇到链接不上chrome的问题

报错信息:

Please ensure that the browser was started successfully with remote debugging port opened.

原因是:由于偷懒我还在使用2021.3版本的webstrom,所以无法链接上新版本的chrome。更新webstorm之后解决。

具体配置方法可以查看:
https://juejin.cn/post/7126550003585122334

2.如何在antd里面优雅的获取element

// 取值
const child = useRef()
<div className="scrollable-container" ref={child}>
</div>

// 调用
child.current

然而,对于react-markdown的MarkDown并不行,会报错:
在这里插入图片描述
不过我没什么代码美学,我就直接用document.getElementsByClassName了,希望有研究的大佬可以教我一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/104860.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows安装最新pip官方教程

在执行pip的pip install --upgrade pip更新时&#xff0c;出现如下错误&#xff0c;怎么也无法重新安装&#xff1a; 根据官网的安装教程来 命令的方式一&#xff1a; • 卸载PIP的命令&#xff1a;python -m pip uninstall pip • 重装PIP的命令&#xff1a;python -m ensure…

Biome-BGC生态系统模型与Python融合技术教程

详情点击公众号链接&#xff1a;Biome-BGC生态系统模型与Python融合技术教程 前言 Biome-BGC是利用站点描述数据、气象数据和植被生理生态参数&#xff0c;模拟日尺度碳、水和氮通量的有效模型&#xff0c;其研究的空间尺度可以从点尺度扩展到陆地生态系统。 在Biome-BGC模型…

thinkphp6项目使用多应用开发

使用composer安装项目&#xff0c;命令&#xff1a;composer create-project topthink/think thinkphp6使用多应用开发&#xff0c;安装多应用扩展&#xff0c;命令&#xff1a;composer require topthink/think-multi-app安装多应用扩展成功后&#xff0c;删除app/controller文…

如何使用 Pinia ORM 管理 Vue 中的状态

状态管理是构建任何Web应用程序的重要组成部分。虽然Vue提供了管理简单状态的技术&#xff0c;但随着应用程序复杂性的增加&#xff0c;处理状态可能变得更具挑战性。这就是为什么像Pinia这样的库被创建出来&#xff0c;以增强Vue的基本状态管理能力。然而&#xff0c;在大型应…

【虚幻引擎UE】UE4/UE5 基于2D屏幕坐标获取场景3D坐标 射线检测(蓝图/C++)

UE4/UE5 基于2D屏幕坐标获取场景3D坐标 一、射线检测1&#xff09;定义1&#xff09;射线与3D场景中的物体交互的流程2&#xff09;射线检测蓝图函数3&#xff09;蓝图实现根据鼠标点击位置获取场景中的坐标值4&#xff09;根据相机中心点获取场景中的坐标值5&#xff09;射线检…

基于SpringBoot的垃圾分类管理系统

基于SpringBootVue的垃圾分类管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven主要功能&#xff1a;包括前台和后台两部分、首页列表展示、垃圾分类、垃圾图谱、查看详…

基于PHP的图像分享社交平台

有需要请加文章底部Q哦 可远程调试 基于PHP的图像分享社交平台 一 介绍 此图像分享社交平台基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。平台角色分为用户和管理员。用户可注册登录&#xff0c;发布图像&#xff0c;修改个人信息&#xff0c;评论图像…

lua-web-utils和proxy程序示例

以下是一个使用lua-web-utils和https://www.duoip.cn/get_proxy的爬虫程序示例。每行代码后面都给出了详细的中文解释。 -- 导入所需的库 local http require("http") local ltn12 require("ltn12") local json require("json") local web_u…

Python 编写 Flink 应用程序经验记录(Flink1.17.1)

目录 官方API文档 提交作业到集群运行 官方示例 环境 编写一个 Flink Python Table API 程序 执行一个 Flink Python Table API 程序 实例处理Kafka后入库到Mysql 下载依赖 flink-kafka jar 读取kafka数据 写入mysql数据 flink-mysql jar 官方API文档 https://nigh…

软件工程17-18期末试卷

2.敏捷开发提倡一个迭代80%以上的时间都在编程&#xff0c;几乎没有设计阶段。敏捷方法可以说是一种无计划性和纪律性的方法。错 敏捷开发是一种软件开发方法论&#xff0c;它强调快速响应变化、持续交付有价值的软件、紧密合作和适应性。虽然敏捷方法鼓励迭代开发和灵活性&…

腾讯云国际-如何使用对象存储COS在 CKafka 控制台创建数据异步拉取任务?腾讯云代充

操作场景 Datahub 支持接入各种数据源产生的不同类型的数据&#xff0c;统一管理&#xff0c;再分发给下游的离线/在线处理平台&#xff0c;构建清晰的数据通道。 本文以 COS 数据为例介绍如何在 CKafka 控制台创建数据异步拉取任务&#xff0c;并对任务进行修改配置&#xf…

CVE-2023-46227 Apache inlong JDBC URL反序列化漏洞

项目介绍 Apache InLong&#xff08;应龙&#xff09;是一站式、全场景的海量数据集成框架&#xff0c;同时支持数据接入、数据同步和数据订阅&#xff0c;提供自动、安全、可靠和高性能的数据传输能力&#xff0c;方便业务构建基于流式的数据分析、建模和应用。 项目地址 h…

Python 中的 Pexpect

我们将通过示例介绍Python中的Pexpect。 Python 中的 Pexpect Python 是一种非常流行的语言&#xff0c;用于数据科学和机器学习。 它是一种非常强大的语言&#xff0c;因为 Python 具有可用于不同目的的内置库。 在这篇文章中&#xff0c;我们将研究Python中的pexpect。 Pex…

性能测试 —— Jmeter 常用三种定时器!

1、同步定时器 位置&#xff1a;HTTP请求->定时器->Synchronizing Timer 当需要进行大量用户的并发测试时&#xff0c;为了让用户能真正的同时执行&#xff0c;添加同步定时器&#xff0c;用户阻塞线程&#xff0c;知道线程数达到预先配置的数值&#xff0c;才开始执行…

k8s客户端配置

K8s客户端安装 前提 K8s服务部署成功&#xff0c;如下 角色 IP地址 操作系统 主机名 Kubernetes版本 master节点 172.16.4.167 CentOS 7.9 k8s-master01 v1.28.2 工作节点1 172.16.4.168 CentOS 7.9 k8s-worker01 v1.28.2 工作节点2 172.16.4.169 CentOS 7.9…

竞赛选题 深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

2.7每日一题(分段函数不定积分)

1、分段函数不定积分是分段做&#xff0c;但分段得出原函数后必须写成C1&#xff0c;C2&#xff0c;C3&#xff1b;&#xff08;直接全部写成C是错误的&#xff09; 2、又因为原函数可导&#xff0c;可导必连续&#xff0c;所以根据原函数在分界点的连续性找到C1&#xff0c;C…

吃豆人C语言开发—Day2 需求分析 流程图 原型图

目录 需求分析 流程图 原型图 主菜单&#xff1a; 设置界面&#xff1a; 地图选择&#xff1a; 游戏界面&#xff1a; 收集完成提示&#xff1a; 游戏胜利界面&#xff1a; 游戏失败界面 死亡提示&#xff1a; 这个项目是我和朋友们一起开发的&#xff0c;在此声明一下…

Qt 窗口的尺寸

本文通过多个案例&#xff0c;详细说明关于Qt窗体尺寸的一些重要问题 默认尺寸 对于一个Qt的窗口&#xff08;继承于QWidget&#xff09;&#xff0c;获取其窗体尺寸的方法size()&#xff1b; 以一个Qt创建Qt Widgets Application项目的默认生成代码为基础&#xff0c;做如下…

实现基于 Jenkins 的多服务器打包方案

实现基于 Jenkins 的多服务器打包方案 在实际项目中&#xff0c;我们经常会遇到需要将一个应用程序或服务部署到不同的服务器上的需求。而使用 Jenkins 可以很方便地自动化这个过程。 设置参数 首先&#xff0c;我们需要设置一些参数&#xff0c;以便在构建过程中指定要部署…