1. 引言

编写Markdown文档现在可以说是程序员的必备技能了,因为Markdown很好地实现了内容与排版分离,可以让程序员更专注于内容的创作。现在很多技术文档,博客发布甚至AI文字输出的内容都是以Markdown格式的形式输出的。那么,Markdown文档如何渲染成标准的Web网页呢?这就要借助于一些支持Markdown格式的编辑器组件了。开源的Markdown编辑器组件有不少,Vditor是笔者认为功能比较全的一款,在这里本文就通过Vditor来实现将一个Markdown文档渲染成网页的具体案例。

阅读本文可能需要的前置文章:《使用Vite创建一个动态网页的前端项目》

2. 实现

使用VS Code打开Vite初始化的工程,并且准备一个Markdown文档(笔者这里用的是Vditor的说明文档),文件组织如下所示:

my-native-js-app

├── public

│ └── demo.md

├── src

│ ├── main.js

│ └── style.css

├── index.html

└── package.json

打开终端,输入如下指令,安装Vditor依赖包:

npm install vditor --save

2.1 基本配置

修改index.html中的内容:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app">
<div id="md-content"></div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

其中,名称为md-content的元素就是用来显示Markdown文档的容器。接着是style.css:

#app {
width: 800px;
margin: 0 auto;
}

也非常简单,就是限制了显示Markdown文档的宽度以及设置居中显示。对于一个文字为主的网页来说,文档的宽度不宜太宽,现代主流的文档网页的设计都是600~800px左右。

最后就是关键的实现主要功能代码main.js:

import "./style.css";
import "vditor/dist/index.css";
import Vditor from "vditor"; async function initMarkdown() {
try {
const response = await fetch("/demo.md");
if (!response.ok) {
throw new Error("网络无响应");
}
const demoMd = await response.text(); // 显示内容
Vditor.preview(document.getElementById("md-content"), demoMd, {
markdown: {
toc: false,
mark: true, //==高亮显示==
footnotes: true, //脚注
autoSpace: true, //自动空格,适合中英文混合排版
},
math: {
engine: "KaTeX", //支持latex公式
inlineDigit: true, //内联公式可以接数字
},
hljs: {
style: "github", //代码段样式
lineNumber: true, //是否显示行号
},
anchor: 2, // 为标题添加锚点 0:不渲染;1:渲染于标题前;2:渲染于标题后
lang: "zh_CN", //中文
theme: {
current: "light", //light,dark,light,wechat
},
transform: (html) => {
// 使用正则表达式替换图片路径,并添加居中样式及题注
return html.replace(
/<img\s+[^>]*src="\.\/([^"]+)\.([a-zA-Z0-9]+)"\s*alt="([^"]*)"[^>]*>/g,
(match, p1, p2, altText) => {
// const newSrc = `${backendUrl}/blogs/resources/images/${postId}/${p1}.${p2}`;
const newSrc = `${p1}.${p2}`;
const imgWithCaption = `
<div style="text-align: center;">
<img src="${newSrc}" class="center-image" alt="${altText}">
<p class="caption">${altText}</p>
</div>
`;
return imgWithCaption;
}
);
},
});
} catch (error) {
console.error("初始化Markdown失败:", error);
}
} document.addEventListener("DOMContentLoaded", initMarkdown);

实现的过程很简单,就是在以文本的形式fetch远端的Markdown文档数据,然后使用Vditor.preview接口将获取的文本数据初始化到HTML的md-content元素中。关键是这个接口的初始化配置参数,具体的代表的功能笔者已经注释到代码中。其他的配置参数非常简单,按需进行配置即可;有点特别的是这个transform参数:

transform: (html) => {
// 使用正则表达式替换图片路径,并添加居中样式及题注
return html.replace(
/<img\s+[^>]*src="\.\/([^"]+)\.([a-zA-Z0-9]+)"\s*alt="([^"]*)"[^>]*>/g,
(match, p1, p2, altText) => {
// const newSrc = `${backendUrl}/blogs/resources/images/${postId}/${p1}.${p2}`;
const newSrc = `${p1}.${p2}`;
const imgWithCaption = `
<div style="text-align: center;">
<img src="${newSrc}" class="center-image" alt="${altText}">
<p class="caption">${altText}</p>
</div>
`;
return imgWithCaption;
}
);
},

2.2 图片居中+题注

这个配置参数是用来配置渲染成HTML页面前的回调函数,我们可以用这个回调函数做什么呢?很简单,可以用来实现一些特殊的元素设计。比如说,Markdown格式标准非常简陋,只规定了如何引入图片,但是没有规定如何设置图片的样式。HTML的文档流是从上到下、从左到右的线性布局的,默认情况下图片是放在新的一行的最左边的。但是实际上更最美观的实现是图片居中展示,并且显示题注。

例如,在这里笔者的Markdown文档中图片相关的内容及最终实现效果是:

在transform指定的回调函数中,也就是这里的html其实是个HTML字符串:

<h3 id="图片">图片<a id="vditorAnchor-图片" class="vditor-anchor" href="#图片"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a></h3>
<pre><code>![alt 文本](http://image-path.png)
![alt 文本](http://image-path.png &quot;图片 Title 值&quot;)
</code></pre>
<p><img src="./head.jpg" alt="案例图片" /></p>

要实现图片居中,并且增加图片题注就很简单了,通过正则表达式搜索到图片的元素<img src="./head.jpg" alt="案例图片" />,将其替换成带题注并且居中的div元素,也就是:

const imgWithCaption = `
<div style="text-align: center;">
<img src="${newSrc}" class="center-image" alt="${altText}">
<p class="caption">${altText}</p>
</div>
`;

最终,这个Markdown文档的图片的网页渲染效果就是:

2.3 图片源更换

笔者实现的另外一个定制功能就是实现更换图片源地址。如果我们经常编写Markdown文档就知道,因为Markdown格式是文字与图片分离的,因此对图片资源的管理是件很麻烦的事情:如果使用base64编码嵌到Markdown文档里,就会影响可读性;如果使用在线图床,要么花钱要么花精力,要么既花钱又花精力。所以笔者还是推荐使用本地相对地址,例如:

![自定义Markdown文档图片元素的默认样式](./2.jpg)

这样的写法,先保证本地文档能正常工作。但是Markdown文档在渲染成网页后这个相对地址就不一样生效了,往往需要对图片地址进行更换。更关键的是,像图片这种稍微重一点的资源最好放到CDN上,所以图片源地址的更换就是个强需求,也就是这部分代码的意思:

return html.replace(
/<img\s+[^>]*src="\.\/([^"]+)\.([a-zA-Z0-9]+)"\s*alt="([^"]*)"[^>]*>/g,
(match, p1, p2, altText) => {
// const newSrc = `${backendUrl}/blogs/resources/images/${postId}/${p1}.${p2}`;
const newSrc = `${p1}.${p2}`;
//...
}
);

先用正则表达式找到图片元素的内容,然后对图片地址进行更换,更换成域内的短地址,也可以使用域外的长地址。也就是不要在Markdown文档本身下功夫,保证本地可以正常显示即可,更多的具体的定制功能通过Vditor渲染前回调来实现。

3. 结语

这个案例最终的显示效果如下所示:

甚至可以表现脑图、流程图、时序图、甘特图、图表、五线谱、流程图等:

不得不说Vditor不一定是所有Markdown编辑器中最好用,但一定是功能比较全的编辑器了,至少比笔者使用过的tui.editor要强不少。其实通过这个功能,你就可以大致实现一个技术博客网站了。具体思路是:把这个渲染过程工具化,将Markdown格式的博客文档批量生成静态网页,然后通过Web服务器进行发布;其实这也是一些静态博客网站工具的实现思路。

实现代码

使用Vditor将Markdown文档渲染成网页(Vite+JS+Vditor)的更多相关文章

  1. 超级简单!把文档转换成网页格式(Core)

    因为需要把好多的文档放在服务器上访问,最开始是使用第3方网站www.aconvert.com,这个其实也挺方便的, 最后由于一些需求原因,最终只有依靠代码来进行,以下是简化后的代码 创建一个控制台应用 ...

  2. C#实现生成Markdown文档目录树

    前言 之前我写了一篇关于C#处理Markdown文档的文章:C#解析Markdown文档,实现替换图片链接操作 算是第一次尝试使用C#处理Markdown文档,然后最近又把博客网站的前台改了一下,目前 ...

  3. 使用Python从Markdown文档中自动生成标题导航

    概述 知识与思路 代码实现 概述 Markdown 很适合于技术写作,因为技术写作并不需要花哨的排版和内容, 只要内容生动而严谨,文笔朴实而优美. 为了编写对读者更友好的文章,有必要生成文章的标题导航 ...

  4. vscode使用Markdown文档编写

    首先安装vscode工具,具体的使用可以参考之前的博文:<Visual Studio Code教程:基础使用和自定义设置> VScode已经默认集成markdown文档编辑插件.可以新建一 ...

  5. 使用vs code编写Markdown文档以及markdown语法详解

    首先安装vscode工具,下载地址如下: https://code.visualstudio.com/ 在vs code的扩展中安装: Markdown Preview Enhanced 这款插件,安 ...

  6. js 加载并解析Markdown文档

    网上有很多网站会通过.md文档来做页面内容(比如,阮一峰老师的es6入门blog: http://es6.ruanyifeng.com/),很好奇,这是怎么做的?(至于.md是什么,或许(https: ...

  7. 小技巧:用 GitBook 组织 Markdown 文档

    喜欢用 Markdown 写文档,那怎么把一个个 Markdown 文档组成在一起呢? 这篇文章,分享了一个用 GitBook 来组织 Markdown 文档的办法.一起了解下吧. Markdown ...

  8. VS Code 搭建合适的 markdown 文档编写环境

    写在开头,之前我是使用Gitee与Github作为图床和Picgo搭配Typora使用的 ,但因为最近觉得这样还是稍微比较繁琐,然后因为VS Code是我的主要文本编辑器.Cpp,Python等均是在 ...

  9. python实现解析markdown文档中的图片,并且保存到本地~

    背景 前阵子简书好像说是凉了,搞得我有点小慌,毕竟我的大部分博客都是放在简书上面的,虽然简书提供了打包导出功能,但是只能导出文字,图片的话还是存在简书服务器上面,再加上我一直想要重新做一个个人博客,于 ...

  10. 使用 VS Code 撰写 Markdown 文档

    众所周知, VS Code 是微软和社区一起开发的一款很优秀的高级代码编辑器.它不仅可以写出一手好代码,还能写出一篇好文章.利用 Markdown 就可以写出一篇排版美观的技术文章了. 而 Markd ...

随机推荐

  1. 提供一个纯C语言的图像压缩程序,但是要达到将6MB的图片压缩到100KB以内的要求,有损压缩肯定是必须的。同时,要在速度上有所提升,我可以为您提供一个基于多线程的图像压缩程序。

    提供一个纯C语言的图像压缩程序,但是要达到将6MB的图片压缩到100KB以内的要求,有损压缩肯定是必须的.同时,要在速度上有所提升,我可以为您提供一个基于多线程的图像压缩程序. 首先,我们需要了解一下 ...

  2. 删除空白行,报错 raise KeyError(list(np.compress(check, subset))) KeyError: ['姓名']

    之前这段程序跑完后没报错,经调试发现到这一句报错 > df.dropna(subset=['姓名'],inplace=True) 意思是删除'姓名'列 含有'NAN'的行 思考第一步:应该是 d ...

  3. docker配置Nvidia环境,使用GPU

    前言 需要 nvdia driver 安装好,请参考 Ubuntu Nvidia driver驱动安装及卸载 docker 安装 配置 apt 阿里云的镜像源 sudo curl -fsSL http ...

  4. 模板设计模式--java进阶day03

    1.模板设计模式 说到模板,我们第一时间想到的可能就是写作文 不过这样写就是一篇完整的作文,我们应该进行修改 这样修改还会存在一个问题,每一个人写的作文不同,中间的body()无法描述清楚,所以我们要 ...

  5. 【SpringMVC】处理模型数据

    处理模型数据 Spring MVC 提供了以下几种途径输出模型数据: ModelAndView: 处理方法返回值类型为 ModelAndView时, 方法体即可通过该对象添加模型数据 Map 及 Mo ...

  6. 【手机】三星手机刷机解决SecSetupWizard已停止

    三星手机恢复出厂设置之后,出现SecSetupWizard已停止的解决方案 零.问题 我手上有一部同学给的三星 GT-S6812I,这几天搞了张新卡,多余出的卡就放到这个手机上玩去了.因为是获取了ro ...

  7. unigui的程序编译后自动运行傻傻的手动【7】

    我们每次修改unigui程序后,一般需要编译后执行,查看效果.可是每次都要关闭杀掉服务程序,再刷新浏览器才能实现. EMB应该知道这个反人类的做法吧.实际上提供了参数配置:自动kill服务程序,自动打 ...

  8. cnpack导致view快捷键失灵。

    学习d10.3.出现怪问题: 卸载cnpack出现: 这下要用快捷键了.那可不烦透了. 如此就ok了. 鸡蛋好吃,还要知道母鸡如何生蛋的?

  9. 使用Python+SymPy计算无穷级数

    引言 在数学中,级数是指由数列的无限项组成的求和表达式.无穷级数的求和是一个非常重要且具有挑战性的数学问题,特别是在信号处理.物理学和工程学等领域.今天,我们将介绍如何利用 Python 中的 Sym ...

  10. rabbitmq的消息的有顺序性

    一.rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点:或者就一个queue但是对应一个consumer,然后这个consumer内部用内 ...