手把手教你用React搭建Markdown博客编辑器:表格、公式、目录一网打尽_react-markdown
去年项目有个需求:需要展示产品的案例页,要求展示标题、图片、表格、公式;并提取标题生成目录,支持目录跳转、滚动高亮目录需求。考虑到多个案例,并且案例可能多次修改,我决定弄一个markdown编辑器,让公司产品可以在编辑器自由发挥,然后在案例展示页根据需求展示markdown文本。我调研了一些常用的react的markdown编辑器,选择了react-markdown-editor-lite组件作为编辑器工具。react-markdown组件进行编辑器md文本以及案例展示页文本解析。 同时引入remark-gfm插件解析GitHub Flavored Markdown语法,remark-math和rehype-katex插件解析公式。使用markdown-navbar插件识别目录,通过监听滚动事件高亮目录。
项目遇到一些难点:
1.如何保存图片?
对编辑器来说需要插入一个图片url,对用户来说,他上传的是一个图片,那如何将图片转换为地址呢。这里使用的react-markdown-editor-lite插件带的onCustomImageUpload方法,我的处理方法是点击编辑器的图片按钮,弹窗进行图片上传,关闭弹窗时将后端返回的地址插入到编辑器中。实现了用户上传获取后端url地址并成功解析图片。
2.如何滚动时高亮目录?
使用markdown-navbar可以根据markdown内容自动生成目录,通过瞄点可以实现点击目录跳转功能。滚动页面时如何高亮目录呢?很容易想到监听鼠标滚动事件。当标题所在的div元素滚动到离视口一定区域(这里我使用的是距离顶部**位置时,在目录对应的元素上添加高亮的className)。
必要的引入
需要的插件将在文章后面进行介绍,基本都是npm i 插件名进行下载的
import MarkdownIt from \'markdown-it\'import MdEditor from \'react-markdown-editor-lite\'// 导入编辑器的样式import \'react-markdown-editor-lite/lib/index.css\'import ReactMarkdown from \'react-markdown\'import remarkGfm from \'remark-gfm\' // 引入remark-gfm插件来支持GitHub Flavored Markdown语法import remarkMath from \'remark-math\'import rehypeKatex from \'rehype-katex\'import rehypeRaw from \'rehype-raw\' //解析为HTML标签的插件
编辑功能实现
使用MdEditor显示编辑插件,在renderHTML根据需要添加解析html的组件,这里使用的是react-markdown。进一步,react-markdown里解析html也需要详细的插件,这里必要的插件remarkGfm。其次,remarkMath解析数字。rehypeKatex解析latex类似的公式,这里强调一下,最好加上output:\'html\',不然解析的公式样式不对,在页面中显示在底部空白区域。
{ return ( {text} ) }} value={editorValue} onChange={({ html, text }) => { handleEditorChange({ html, text }) }} onCustomImageUpload={handleImageUpload} />
singleTilde: false
这个配置项的作用是不将单波浪线~
解释为数学公式的符号。这样做的好处是可以避免在文本中使用~
表示非数学意义的波浪线时出现误解或错误的数学公式解析。
rehypeRaw
:
rehypeRaw
插件允许你在 HTML 中保留原始的 HTML 结构。这对于一些需要处理未转义的 HTML 内容的情况非常有用。例如,如果你希望保留标签或者自定义的 HTML 元素,你可以使用这个插件。
rehypeKatex
:
rehypeKatex
插件用于支持 KaTeX 数学公式渲染。KaTeX 是一个快速、易于使用的数学公式渲染库,可以用来渲染 LaTeX 格式的数学公式。通过使用rehypeKatex
,你可以方便地在 Markdown 文档中插入数学公式,并且这些公式会在生成的 HTML 中被正确渲染。
{ output: \'html\' }
:
{ output: \'html\' }
是传递给rehypeKatex
插件的配置选项。它指定了输出格式为 HTML。这意味着 KaTeX 渲染后的数学公式将以 HTML 格式嵌入到最终的 HTML 文档中,而不是其他可能的格式(例如 SVG)。
上传图片保存后端图片地址
在onCustomImageUpload方法中添加自定义事件,这里我添加一个antd 的Modal组件,点击编辑器的图片按钮时打开该上传弹框。点击确定后将后端保存的地址嵌入到编辑器文本末尾。
const insertImageToEditor = () => { // 获取当前编辑器的文本 let text = editorValue uploadFileList.map((item) => { if (item.status == \'done\') { const markdownText = `
` text = text.slice(0) + markdownText } }) // 更新编辑器的内容 setEditorValue(text) handleEditorChange({ html: mdParser.render(text), text }) }
md文件转前端页面实现
markdown显示和编辑器类似,都是用react-markdown解析md文本。不一样的是不用包在编译器里面。
这里在components离进行了h1-h3以及p标签的识别,是为了配合UI进行样式自定义。调整显示的文本字体大小、颜色、行高等。同时也是为了能够滚动高亮目录,定位页面的标题元素。
<ReactMarkdown remarkPlugins={[ remarkGfm, [remarkMath, { singleTilde: false }], // 注意这里要传递对象参数 ]} rehypePlugins={[rehypeRaw, [rehypeKatex, { output: \'html\' }]]} components={{ h1({ children }) { return ( {children}
) }, h2({ children }) { return ( {children}
) }, h3({ children }) { return ( {children}
) }, p({ children }) { return {children}
}, }} > {source?.content}
md解析表格无法渲染?
遇到在页面某些无法识别的样式可以手动添加css文件进行样式适配。同时通过查看页面元素,可以为元素设置统一的样式。
table { border: 1px solid #f6f6f6; font-size: 14px; line-height: 1.7; max-width: 100%; overflow: auto; border-collapse: collapse; border-spacing: 0; box-sizing: border-box; color: #333; tr { border: 1px solid #f6f6f6; th { text-align: center; font-weight: 700; border: 1px solid #efefef; padding: 10px 6px; background-color: #f5f7fa; word-break: break-word; } } } td { border: 1px solid #efefef; text-align: left; padding: 10px 15px; word-break: break-word; min-width: 60px; } table tr:nth-child(2n) { background-color: transparent; }
目录功能实现
引入markdown-navbar插件
import MarkNav from \'markdown-navbar\' // markdown 目录
MarkNav自动识别目录结构。
source.content是后端保存的之前编辑器上传的markdown文本。
onNavItemClick方法是点击目录后抛出的方法,在这个方法中实现点击跳转到目录功能
{ scrollToElement(hashValue) }} onHashChange={(newHash, oldHash) => { window.location.hash = newHash }} >
点击目录进行跳转
使用瞄点的方式,点击元素后使用scrollIntoView将元素滚动到视口顶部。但是由于下面的滚动高亮进行了元素的监听,为了避免点击目录的滚动和滚动高亮同时对目录高亮操作。这里在点击目录的时候移除滚动的事件监听
// 滚动到锚点 const scrollToElement = (elementId: string) => { const element = document.getElementById(elementId) if (element) { // 移除滚动事件监听器 contentRef?.current?.removeEventListener(\'scroll\', handleScroll) // 平滑滚动到指定元素 element.scrollIntoView({ behavior: \'smooth\', block: \'start\', }) // 在滚动完成后重新添加滚动事件监听器 setTimeout(() => { contentRef?.current?.addEventListener(\'scroll\', handleScroll) }, 1000) } }
滚动高亮目录功能实现
这里实现的逻辑就是添加滚动事件监听,获取所有的标题元素。对移到视口顶部的元素高亮元素所在目录。
useEffect(() => { return () => { contentRef?.current?.removeEventListener(\'scroll\', handleScroll) markNav?.current?.removeEventListener(\'wheel\', handleWheel) } }, []) useEffect(() => { if (contentRef.current) { contentRef?.current?.addEventListener(\'scroll\', handleScroll) } markNav?.current?.addEventListener(\'wheel\', handleWheel) }, [])
const handleScroll = useCallback(() => { const sections = document.querySelectorAll( \'.case-detail-subtitle, .case-detail-subtitle_3\', ) let closestSection = null let minDistance = Infinity let hightIndex = -1 sections.forEach((section, index) => { const sectionTop = (section as HTMLElement).offsetTop const distanceFromTop = (contentRef.current as HTMLElement).scrollTop + 170 - sectionTop // 可见视口内找到距离顶部最近的元素 if (distanceFromTop >= 0 && distanceFromTop item.classList.remove(\'active\')) if (closestSection) { const hashValue = `#${(closestSection as HTMLElement).id}` window.location.hash = hashValue const menu = document.querySelector(\'.markdown-navigation\') menu?.children[hightIndex].classList.add(\'active\') } }, [contentRef])
对使用到的插件进行介绍
react-markdown-editor-lite编辑器插件
react-markdown-editor-lite
是一个基于 React 的轻量级 Markdown 编辑器组件。它提供了简单易用的接口来创建一个集 Markdown 编辑与预览于一体的编辑器。这个库非常适合那些需要在项目中集成 Markdown 功能的应用,比如博客系统、文档编写工具等。
安装地址 react-markdown-editor-lite - npm
该组件的主要特点包括:
- 轻量级:相比其他复杂的 Markdown 编辑器,
react-markdown-editor-lite
尽可能减少了不必要的依赖和功能,使得其体积较小,加载更快。 - 易于集成:作为 React 组件,它可以轻松地嵌入到任何 React 应用中。
- Markdown 支持:支持标准的 Markdown 语法,同时也可以配置额外的语法扩展。
- 实时预览:用户在编辑 Markdown 文本时,可以实时看到渲染后的效果。
要使用 react-markdown-editor-lite
,首先需要安装它。可以通过 npm 或 yarn 来安装:
npm install react-markdown-editor-lite# 或者yarn add react-markdown-editor-lite
react-markdown插件
react-markdown
是一个用于 React 应用的库,它允许你将 Markdown 格式的文本转换为 HTML,以便在网页上进行渲染。react-markdown
是非常流行的一个库,因为它简单易用,并且支持丰富的 Markdown 语法扩展。
主要特点
- 强大的 Markdown 支持:
react-markdown
支持标准的 Markdown 语法以及许多常用的扩展,如表格、脚注、任务列表等。 - 高度可定制化:你可以自定义如何渲染不同的 Markdown 元素,例如,你可以自定义标题、列表、链接等的样式。
- 易于集成:作为一个 React 组件,
react-markdown
可以很容易地集成到现有的 React 应用中。
安装
你可以通过 npm 或 yarn 来安装 react-markdown
:
npm install react-markdown# 或者yarn add react-markdown
rehype-katex插件
rehype-katex插件的主要作用是将 Markdown 文档中的 LaTeX 公式转换为美观的 HTML 渲染效果。具体来说,
rehype-katex
插件的作用是将 Markdown 中的 LaTeX 公式解析并转换成可以在网页上显示的 HTML 格式的数学公式。
npm install rehype-katex
rehype-raw插件
rehype-raw
是一个用于在使用 Rehype(一个用于处理 HTML 的工具)时允许解析和渲染未经处理的 HTML 代码的插件。默认情况下,Rehype 会过滤掉一些可能不安全的 HTML 标签和属性,以防止 XSS(跨站脚本攻击)。但有时你可能需要保留一些特定的 HTML 结构,这时rehype-raw
就派上用场了。
npm install rehype-raw
remark-math插件
remark-math
是一个用于在 Markdown 中处理数学公式的 Remark.js 插件。Remark.js 是一个用于解析 Markdown 的工具。remark-math
插件使得在 Markdown 文档中使用 LaTeX 数学公式变得更加简单。
npm install remark-math
remark-gfm插件
remark-gfm
是一个用于在 Markdown 中启用 GitHub Flavored Markdown (GFM) 特性的 Remark.js 插件。GitHub Flavored Markdown 是一种扩展了标准 Markdown 的语法,提供了更多的功能,如表格、任务列表等。
npm install remark-gfm