首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >尝试扩展一下 markdown 编辑器的功能渲染及语法

尝试扩展一下 markdown 编辑器的功能渲染及语法

原创
作者头像
繁依Fanyi
修改于 2025-06-29 15:49:58
修改于 2025-06-29 15:49:58
2370
举报

写技术文档的时候,我经常遇到一个问题:Markdown 本身虽然简洁,但在展示复杂内容时就有些力不从心。尤其是想插入一些结构化的内容,比如提示框、图文卡片或者是横向图集,传统的 Markdown 写法总显得局促,甚至需要引入 HTML 标签来“打补丁”。这让我开始思考:能不能在不改变 Markdown 核心语法习惯的前提下,让它具备更现代的展示能力?

于是我动手做了一个试验性的项目,一个带有自定义块语法的 Markdown 渲染器。它的目标是:既保留 Markdown 的书写体验,又能通过语法扩展实现现代化的 UI 表现。我想通过这篇文章,记录这个项目从构思到实现的整个过程。

为什么要扩展 Markdown

最初的起因其实很简单:我需要一个可以在博客或项目文档中,插入「漂亮又实用」的组件,比如:

  • 带图标和强调背景的提示框;
  • 可以左右滑动的图片展;
  • 模块化的特性卡片区域;
  • 能高亮显示重点行的代码框……

而这些,如果单靠 Markdown 原始语法来实现,往往要么写不出来,要么写出来很丑。市面上的 Markdown 渲染器不少,比如 VitePress、Typora、Notion 等,但要么不支持自定义语法,要么需要配合复杂的插件系统。

我想要的,是一种更轻量、可控、原生 Markdown 写法友好的方案。

为了方便修改以及兼容微信公众号,我使用了这个项目 https://github.com/doocs/md 。很 nice 的项目,大家快去 fork。

在这里插入图片描述
在这里插入图片描述

自定义块语法设计

我不打算破坏 Markdown 原有的纯文本特性,因此,我最终选择了一种基于 ::: 的块级标记形式,类似这样:

代码语言:markdown
AI代码解释
复制
::: info-box
type=warning
icon=true
background=#fcf8e3
border-left=4px solid #f0ad4e
::style::
注意事项
::head::
请务必在操作前备份数据
:::

它有三个关键部分:

  • 开头标识符 ::::声明一个自定义块类型(如 info-box)。
  • 样式配置区域:使用类似 CSS 的键值对配置,简单直观。
  • 结构内容区块:使用 ::style:: ::head:: ::split:: 等标记划分内容结构。

这种语法相比 HTML 更易读,也更方便用正则解析。在渲染前,我通过一个正则表达式将其完整提取出来,再交给对应的组件进行处理。

样式解析机制

样式定义部分支持简洁的 key=value 写法,我设计了一个轻量的解析函数:

代码语言:js
AI代码解释
复制
function parseStyleDefinitions(styleText) {
  const styles = {};
  const lines = styleText.split('\n').filter(Boolean);
  lines.forEach(line => {
    const [key, value] = line.split('=').map(part => part.trim());
    if (key && value) styles[key] = value;
  });
  return styles;
}

这个函数的目标很清晰:

  • 解析用户配置的键值对;
  • 自动处理换行、空格等输入问题;
  • 输出一个 JS 对象,供 CSS 渲染模块使用。

所有样式我都使用内联写法,避免全局污染。虽然看起来会稍长,但这带来了高度的隔离性——每一个组件只控制自己的样式,和外界无关,尤其适合嵌入在第三方平台或者静态页面中。

渲染结构的抽象设计

在 Markdown 渲染的过程中,保持结构清晰是一件非常重要的事情。我希望每种自定义块都可以有自己独立的“生命周期”:从解析、到样式处理、再到最终 HTML 输出,都由专属模块完成。这样做最大的好处是:每新增一个新类型的块,不需要动原有逻辑,保持系统的解耦和可维护。

于是我采用了「渲染器组件」的设计思路。每个组件负责一种块类型的渲染,比如:

  • InfoBoxRenderer → 渲染提示框
  • ImageGalleryRenderer → 渲染图集块
  • StructuredCardRenderer → 渲染特性卡片块

每个渲染器接收结构化数据(head/headStyle/splits/styles 等),输出最终 HTML。

横向图片展览模板实现

这里我用“横向图集”这个模板来举例,讲讲整个过程。

首先 Markdown 中用户会写一段这样的内容:

代码语言:markdown
AI代码解释
复制
::: image-gallery-horizontal
gap=16px
padding=24px
radius=12px
shadow=0 4px 12px rgba(0,0,0,0.15)
max-height=280px
::style::
我的摄影作品集
::head::
这是我最近拍摄的一些风景照片
::split::
https://example.com/photo1.jpg
::split::
https://example.com/photo2.jpg
::split::
https://example.com/photo3.jpg
:::

结构上很清晰:

  • 样式定义:gappaddingshadow 等;
  • 标题文本:::style::
  • 引导语:::head::
  • 多张图片:::split:: 一行一张。

然后我在 ImageGalleryRenderer 中实现 HTML 构建方法:

代码语言:js
AI代码解释
复制
function buildImageGalleryHorizontalHtml(head, splits, styles) {
  const galleryStyle = {
    display: 'flex',
    overflowX: 'auto',
    scrollSnapType: 'x mandatory',
    scrollBehavior: 'smooth',
    gap: styles.gap || '16px',
    padding: styles.padding || '16px'
  };

  const imgStyle = {
    flex: '0 0 auto',
    maxHeight: styles['max-height'] || '280px',
    borderRadius: styles.radius || '8px',
    boxShadow: styles.shadow || '0 2px 8px rgba(0,0,0,0.1)',
    objectFit: 'cover'
  };

  return `
    <section>
      ${head ? `<h3>${head}</h3>` : ''}
      <div style="${styleObjectToCss(galleryStyle)}">
        ${splits.map(url => `
          <img 
            src="${url.trim()}" 
            loading="lazy" 
            style="${styleObjectToCss(imgStyle)}"
          >
        `).join('')}
      </div>
    </section>
  `;
}

整个过程有几个关键点:

  • 使用 flexscroll-snap-type 实现横向滚动;
  • 图片加上 loading=lazy,提升性能;
  • 自定义 radiusshadow 等样式可通过用户设置动态应用;
  • 使用 styleObjectToCss() 工具函数生成内联 CSS 字符串,确保样式隔离。

用户写的是纯 Markdown,生成的是带完整样式的图集 UI,这种体验我自己在写博客时觉得非常顺手。


工具函数:样式对象转 CSS 字符串

渲染器里一个常用的小函数是 styleObjectToCss,它将 JS 对象格式的样式转为内联 CSS 字符串:

代码语言:js
AI代码解释
复制
function styleObjectToCss(styleObj) {
  return Object.entries(styleObj)
    .map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`)
    .join('; ');
}

比如:

代码语言:js
AI代码解释
复制
{
  maxHeight: "280px",
  borderRadius: "8px"
}

会变成:

代码语言:css
AI代码解释
复制
max-height: 280px; border-radius: 8px;

这样我们可以在构建 HTML 的时候不用关心 CSS 写法细节,直接操作 JS 对象即可。


结构化卡片组件的实现

为了让 Markdown 中也能出现像 Landing Page 一样的“特性展示区”,我设计了一个叫 structured-card 的块类型。它的语法类似这样:

代码语言:markdown
AI代码解释
复制
::: structured-card
background=#f8f9fa
border-radius=12px
padding=24px
shadow=0 6px 20px rgba(0,0,0,0.1)
::style::
产品特性
::head::
我们的产品具备以下核心优势:
::split::
界面友好、逻辑清晰、零学习成本。
::split::
高度可定制,适应不同团队工作流。
::split::
精简核心逻辑,渲染与交互表现优异。
:::

每个 ::split:: 段落渲染为一张卡片,整体可以是网格、也可以是横向滑动。这样,我们只需要专注于内容组织,不用考虑样式。组件内部会自动决定如何排版布局,并生成响应式设计

在这里插入图片描述
在这里插入图片描述

我在卡片模块中默认加入了标题加粗、行距控制、边距控制,最大限度保持文本的可读性和信息层次感。

在这里插入图片描述
在这里插入图片描述

信息提示框组件(info-box)

技术文档里经常要写提示,比如“注意事项”、“操作前请备份”、“这是 Beta 版本”这种。我不希望写一堆冗长的 HTML,只想像这样:

代码语言:markdown
AI代码解释
复制
::: info-box
type=info
icon=true
background=#e7f3fe
border-left=4px solid #2196f3
::style::
温馨提示
::head::
请使用稳定网络环境完成此次操作
:::

渲染结果是:

  • 有左侧蓝条;
  • 图标默认用的是内置的 SVG icon;
  • 背景柔和;
  • 所有提示样式可以通过 type 参数控制(比如 danger、warning、success、info 等)。

我把所有 info-box 的样式配置都集中在 defaultInfoBoxStyleMap 中,用户通过 type 选择即可复用。


渲染结构图:组件与语法之间的关系

为了更清晰地让大家理解各部分的关系,我绘制了一张结构图,展示从 Markdown 源文件到最终页面的处理流程:

在这里插入图片描述
在这里插入图片描述

这张图也揭示了在修改和匹配渲染设计上的理念:解析与渲染分离,每个模块单独负责自己的逻辑,让整个系统既清晰又容易维护。


整个部分没有用到什么高深的前端技术,也没有炫酷动画和复杂状态管理,但它确实给我带来了满足感:

  • 让我重新审视 Markdown 的表达边界;
  • 让我思考写作者和阅读者之间的微妙平衡;
  • 更让我意识到一个简单的写作工具,如果能多照顾一点“内容组织感”,就能极大提升表达效率。

有时候我们太依赖工具,却忽略了语言结构本身的设计也很重要。Markdown 是语言,也是一种接口,我想通过这里,延伸它的语义范围,又不破坏它的核心简洁美。

最后给大家推荐图床三件套 Github + PicGo + jsdelivr,轻松搭建简易可用的图床。

在这里插入图片描述
在这里插入图片描述

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要扩展 Markdown
  • 自定义块语法设计
  • 样式解析机制
  • 渲染结构的抽象设计
  • 横向图片展览模板实现
  • 工具函数:样式对象转 CSS 字符串
  • 结构化卡片组件的实现
  • 信息提示框组件(info-box)
  • 渲染结构图:组件与语法之间的关系
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档