本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(2)

【前端】React使用react-markdown+antd实现引入渲染markdown文件 react使用react-markdown渲染markdown文件,手动生成导航栏目录

发布于2023-03-17 02:37     阅读(1332)     评论(0)     点赞(25)     收藏(3)


项目中遇见一个需求,要求直接在浏览器打开markdown文件进行预览,初次使用遇见一些坎坷,以下记录实现过程,将其封装成了一个组件。

1.下载依赖

  1. yarn add react-markdown
  2. //其余样式插件:
  3. yarn add remark-gfm
  4. yarn add rehype-raw
  5. yarn add react-syntax-highlighter
  6. yarn add github-markdown-css

react-markdown是github上的一款开源的适用于markdown文件的组件。

2.引入使用

2.1 获取markdown文件内容

此处使用fetch来获取文件内容,我在项目中直接引入无法被识别,当然大家也可以试试直接引入的方式。

  1. import React from 'react';
  2. import ReactMarkdown from 'react-markdown';//引入
  3. //import md from './README.md';
  4. const App=({url})=>{
  5. const [mdContent, setMdContent] = useState('')
  6. useEffect(() => {
  7. //url是markdown文件的路径,我在项目中是放到了media文件夹下,示例:url为'/media/xx.md'
  8. fetch(url)
  9. .then(res => res.text())
  10. .then(text => setMdContent(text));
  11. }
  12. }, []);
  13. return(
  14. <ReactMarkdown
  15. className='markdown-body'
  16. children={mdContent}
  17. />
  18. )
  19. }
  20. export default App

实现效果(以我的vue学习笔记做个示例...)

3.样式美化

此时已经可以实现markdown文件的读取了,但是样式单一,表格、标题、代码块等各种样式都没有展示出来,并且也没有像Typora等编辑器那样的导航栏。导航栏部分放到文章最后进行讲述,接下来进行内容的美化。

仍然是通过插件来实现(使用的是标题1.下载依赖中的 “其余样式插件” )

  1. import React from 'react';
  2. import ReactMarkdown from 'react-markdown';//引入
  3. //import md from './README.md';
  4. import remarkGfm from 'remark-gfm';// 划线、表、任务列表和直接url等的语法扩展
  5. import rehypeRaw from 'rehype-raw'// 解析标签,支持html语法
  6. import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' // 代码高亮
  7. //高亮的主题,还有很多别的主题,可以自行选择
  8. import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'
  9. const App=({url})=>{
  10. const [mdContent, setMdContent] = useState('')
  11. useEffect(() => {
  12. //url是markdown文件的路径,我在项目中是放到了media文件夹下,示例:url为'/media/xx.md'
  13. fetch(url)
  14. .then(res => res.text())
  15. .then(text => setMdContent(text));
  16. }
  17. }, []);
  18. return(
  19. <ReactMarkdown
  20. className='markdown-body'
  21. children={mdContent}
  22. remarkPlugins={[remarkGfm, { singleTilde: false }]}
  23. rehypePlugins={[rehypeRaw]}
  24. components={{
  25. code({ node, inline, className, children, ...props }) {
  26. const match = /language-(\w+)/.exec(className || '')
  27. return !inline && match ? (
  28. <SyntaxHighlighter
  29. children={String(children).replace(/\n$/, '')}
  30. style={tomorrow}
  31. language={match[1]}
  32. PreTag="div"
  33. {...props}
  34. />
  35. ) : (
  36. <code className={className} {...props}>
  37. {children}
  38. </code>
  39. )
  40. }
  41. }}
  42. />
  43. )
  44. }
  45. export default App

标签倒是被解析出来了。表格部分也整齐了许多,但是代码块的背景和表格线条等样式统统没有。

查阅之后发现是没有引入内容样式,github-markdown-css,这个可以直接使用yarn或npm进行安装,引入一下:

import 'github-markdown-css';

 现在就可以得到一份完美的markdown格式内容了。

4.导航栏目录实现

关于目录的实现,也有对应的插件可以直接进行生成,markdown-navbar,很多文章都说非常好用,懒人插件,但是这个插件对markdown文件编写格式有要求,格式不规范的话识别会出错,导致将文章内容或者备注也识别为标题。这里也简单介绍一下使用。

首先安装

yarn add markdown-navbar

使用。目录想要显示在导航栏左侧或固定在某处等等,都可以自行调整样式。

  1. import MarkNav from 'markdown-navbar';
  2. const App=({url})=>{
  3. const [mdContent, setMdContent] = useState('')
  4. useEffect(() => {
  5. //url是markdown文件的路径,我在项目中是放到了media文件夹下,示例:url为'/media/xx.md'
  6. fetch(url)
  7. .then(res => res.text())
  8. .then(text => setMdContent(text));
  9. }
  10. }, []);
  11. return(
  12. <div className='nav-menu'>
  13. <MarkNav
  14. className="article-menu"
  15. source={mdContent}
  16. headingTopOffset={80}
  17. ordered={false} //是否显示标题题号1,2等
  18. />
  19. </div>
  20. <div className='content-box'>
  21. //markdown内容
  22. </div>
  23. )
  24. }
  25. export default App

 接下来是手动实现markdown目录导航栏的方式。

本文是使用antd的组件Anchor进行了目录的实现。

首先遍历了markdown文件中所有的内容,取出所有h1-h6标签以及标签中的文本进行目录的展示,在每个标题标签中加入一个a标签作为锚点,方便点击标题跳转时进行定位。方法如下。

  1. const [titles, setTitles] = useState([])
  2. const addAnchor = () => {
  3. const ele = document.getElementsByClassName('markdown-body')[0];
  4. let eid = 0;
  5. let titles = [];
  6. for (const e of ele.childNodes) {
  7. if (e.nodeName === 'H1' || e.nodeName === 'H2' || e.nodeName === 'H3' || e.nodeName === 'H4' || e.nodeName === 'H5' || e.nodeName === 'H6') {
  8. let a = document.createElement('a');
  9. a.setAttribute('id', '#' + eid);
  10. a.setAttribute('class', 'anchor-title');
  11. a.setAttribute('href', '#' + eid);
  12. a.innerText = ' '
  13. let title = {
  14. type: e.nodeName,
  15. id: eid,
  16. name: e.innerText
  17. };
  18. titles.push(title);
  19. e.appendChild(a);
  20. eid++;
  21. }
  22. }
  23. return titles;
  24. }

 使用antd的Anchor组件,Link的href与上述加入的a标签中的href相同,title就是标题标签中取出的文本内容,为每个Link增加一个类名,如此处h1标签类名为title-H1,h2标签类名为title-H2...方便区分修改样式。

  1. <Anchor
  2. className='markdown-nav'
  3. affix={false}
  4. onClick={handleClickFun}
  5. getContainer={() => document.getElementsByClassName('content-box')}
  6. >
  7. {
  8. titles.map(t => (
  9. <Link href={'#' + t.id} title={t.name} className={'title-' + t.type} key={t.id} />
  10. ))
  11. }
  12. </Anchor>

为Anchor绑定点击事件,点击标题的时候,让内容也对应定位到标题所在的地方。

测试发现,antd的Anchor组件在点击锚点后会修改URL,将当前点击的herf拼接到路由上,而单页应用中如果使用哈希模式的路由,当URL被修改后,刷新页面会导致当前路由没有定义而出现404的情况。

查阅antd官方文档。 发现该组件点击事件的回调函数第一个参数就是事件,那么就通过e.preventDefault()阻止掉默认事件,然后再通过第二个参数拿到点击的href,通过document.getElementById获取到这个元素,然后使用scrollIntoView添加页面滚动效果。

  1. const handleClickFun = (e, link) => {
  2. e.preventDefault();
  3. if (link.href) {
  4. // 找到锚点对应得的节点
  5. let element = document.getElementById(link.href);
  6. // 如果对应id的锚点存在,就跳滚动到锚点顶部
  7. element && element.scrollIntoView({ block: 'start', behavior: 'smooth' });
  8. }
  9. }

以下是完整实现效果,标题导航栏的缩进自行通过样式调整即可,本文是让二级标题缩进1em,三级标题缩进2em,依次类推...

原文链接:https://blog.csdn.net/Sakuraaaa_/article/details/128400497




所属网站分类: 技术文章 > 博客

作者:Bhbvhbbgg

链接:http://www.qianduanheidong.com/blog/article/505419/cc43ce40c01251362ae6/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

25 0
收藏该文
已收藏

评论内容:(最多支持255个字符)