Markdown 与 HTML 混合使用完全指南

Markdown 写起来很舒服,但总会碰到它搞不定的排版需求——比如让图片居中、设置文字颜色、做个可折叠的内容区域,或者用 HTML 表格实现跨行合并。这时候你可能会搜 "markdown in html" 或 "html in markdown",想知道这两种格式能不能混着用。

答案是肯定的。Markdown 的设计者 John Gruber 在最初设计时就明确说过:HTML 是 Markdown 的发布格式,Markdown 不是 HTML 的替代品,而是它的补充。所以在 Markdown 文件里直接写 HTML 标签,不是什么 hack,而是被官方鼓励的做法。

不过这里面有不少细节值得注意。比如,行内标签和块级标签的行为完全不同;有些平台支持 markdown="1" 属性让 HTML 标签内部的 Markdown 语法也能生效,但有些平台完全忽略它。我在这篇文章里会把这些规则和差异讲清楚,并分享一些实际使用中的经验。

Markdown HTML 混合使用示意图

核心规则:行内标签 vs 块级标签

理解 Markdown 和 HTML 混用的关键,在于搞清楚两类 HTML 标签在 Markdown 中的不同行为。

行内标签(Inline Tags)—— 随便用,Markdown 正常解析

行内标签也叫 span-level 标签,比如 <span><em><strong><a><code><br> 等。这些标签可以在 Markdown 文本中任意位置使用,而且不影响周围的 Markdown 语法解析。

这是一个 <span style="color:red">红色文字</span> 的例子。

这行字里 <em>这里用 HTML 斜体</em> 而 **这里用 Markdown 加粗**,两者和平共处。

点击 <a href="https://example.com" target="_blank">这个链接</a> 在新窗口打开。

上面的写法在几乎所有 Markdown 解析器中都能正常工作,因为行内标签被当作 Markdown 文本的一部分来处理。

块级标签(Block-level Tags)—— 内部 Markdown 会被忽略

块级标签是另一回事。<div><table><p><pre><form> 这些标签一旦出现,Markdown 解析器会认为这个区域不是 Markdown 了,里面的内容直接按原始 HTML 处理。

下面这样写,**加粗** 不会生效:

<div>
这段话里的 **加粗** 不会被渲染,*斜体* 也不会。
</div>

这个行为是 Markdown 规范(包括原始规范和 CommonMark)的明确定义,不是某个解析器的 bug。Markdown 解析器在遇到块级 HTML 标签时,会停止解析,直接把原始 HTML 原样输出。

我第一次碰到这个问题是在写 GitHub README 的时候。当时想用 <div> 做一个两列布局,结果发现 div 里面所有的 Markdown 链接和格式全部失效了,变成了一坨纯文本。后来才知道,这是 Markdown 解析器的设计行为。

用空行隔开块级标签和 Markdown 内容

有一个容易忽略的规则:块级 HTML 标签的前后需要用空行与 Markdown 内容隔开。如果不隔开,有些解析器会把 HTML 标签当作普通文本来处理。

<!-- 正确写法:前后有空行 -->

前面的 Markdown 内容。

<div>
<p>HTML 块级内容</p>
</div>

后面的 Markdown 内容。

另外,块级 HTML 标签不要用空格或制表符缩进。如果缩进了 4 个空格或 1 个制表符,Markdown 会把它当成代码块来处理。

常见用法:用 HTML 补充 Markdown 做不到的事

既然 Markdown 支持嵌入 HTML,那具体有哪些场景会用到?这里列几个最常见的。

控制图片大小和位置

Markdown 的图片语法 ![alt](url) 没有指定尺寸和对齐方式的选项。如果你想控制图片的显示大小或者让它居中,就得用 HTML。

<!-- 设置图片宽度 -->
<img src="image.png" width="300" alt="示例图片">

<!-- 图片居中 -->
<div align="center">
<img src="image.png" width="80%" alt="居中图片">
</div>

在 GitHub 上,<img> 标签的 widthheight 属性是支持得比较好的。不过 align 属性属于旧式 HTML,有些平台可能会忽略,更现代的做法是用 CSS 的 style 属性。

文字颜色和样式

Markdown 没有设置文字颜色的语法。你可以用 <span> 配合 style 来实现:

<span style="color: red;">红色文字</span>
<span style="color: #0066cc; font-size: 18px;">蓝色大字</span>
<span style="background-color: yellow;">黄底高亮</span>

说实话,这种方式在纯 Markdown 编辑器里看着挺别扭的,但渲染出来的效果确实能用。我的建议是,如果你只是偶尔需要改个颜色,用 <span> 就够了;如果整个文档都需要复杂的样式控制,那可能应该直接写 HTML 而不是 Markdown。

可折叠内容(details/summary)

<details><summary> 这两个 HTML 标签在 GitHub、GitLab、CSDN 等平台上支持得很好,可以做出可折叠/展开的内容区域,非常适合放一些补充信息或长代码块。

<details>
<summary>点击展开查看详细代码</summary>

```python
def hello():
    print("Hello, World!")


注意 `<summary>` 标签里的文字会作为折叠区域的标题显示。这个技巧在做 README 文档时特别实用,把安装步骤或常见问题折叠起来,页面看起来清爽很多。

### 键盘按键样式

如果你在写技术文档,可能需要展示键盘快捷键。`<kbd>` 标签正好派上用场:

```markdown
按 <kbd>Ctrl</kbd> + <kbd>C</kbd> 复制,<kbd>Ctrl</kbd> + <kbd>V</kbd> 粘贴。

大多数 Markdown 渲染器会把 <kbd> 显示为带边框的小方块,看起来像真实的键盘按键。

页内锚点跳转

Markdown 的链接语法 [文字](#锚点) 支持页内跳转,但你需要先定义锚点位置。可以用 HTML 的 id 属性来实现:

<div id="section-1"></div>

## 第一个章节

这里是章节内容...

[跳转到第一个章节](#section-1)

在 GitHub 上,标题会自动生成锚点(基于标题文本),所以这个技巧主要用于非标题位置。

进阶技巧:让 HTML 标签内部的 Markdown 也生效

前面说了,块级 HTML 标签内部的 Markdown 语法默认不被解析。但有些场景确实需要在 HTML 标签里写 Markdown,比如用 <div> 做布局的同时还想用 Markdown 的链接和格式。

方案一:加空行(CommonMark 规范)

CommonMark 规范(GitHub、GitLab、很多现代解析器使用)支持一种方式:在 HTML 标签和内容之间加空行,让解析器知道里面的内容应该当作 Markdown 来处理。

<div>

这段话里的 **加粗** 会正常渲染。

- 列表项一
- 列表项二

</div>

关键是开始标签 <div> 后面、内容前面,以及内容后面、结束标签 </div> 前面,都要有空行。我实测在 GitHub 上这个方式是生效的。

方案二:markdown 属性(Python-Markdown、Markdown Extra)

Python-Markdown 的 md_in_html 扩展和 PHP Markdown Extra 支持在 HTML 标签上添加 markdown 属性,明确告诉解析器内部内容按 Markdown 处理。

<div markdown="1">

这里的 **Markdown** 会正常渲染。

</div>

markdown 属性支持三种值:

效果
"1"使用标签的默认行为(div 用 block 模式,p 用 span 模式)
"block"强制块级模式,段落、标题、列表都会正常渲染
"span"强制行内模式,只渲染加粗、斜体、链接等行内语法

需要注意的是,这个属性不是标准 Markdown 规范的一部分,只有在支持它的解析器上才能用。比如 GitHub 的渲染器就不支持这个属性(至少目前不支持),但 GitHub Pages 的 Jekyll/Kramdown 环境是支持的。

方案三:用 <span> 替代 <div>

因为行内标签内部的 Markdown 不受影响,一个取巧的办法是用 <span> 代替 <div>,然后通过 CSS 让它表现为块级元素。

<span style="display: block; border: 1px solid #ccc; padding: 10px;">

这段话里的 **Markdown** 会正常渲染,因为 span 是行内标签。

</span>

这个方法兼容性比较好,但语义上不够严谨——<span> 语义上是行内元素,强制改成块级在可访问性方面不太好。

平台兼容性对比

不同平台对 Markdown 中 HTML 的支持程度差异很大。我根据实际测试和官方文档整理了下面的对比表。

功能GitHubGitLabJekyll/KramdownPython-MarkdownObsidianTypora
行内 HTML 标签支持支持支持支持支持支持
块级 HTML 标签支持支持支持支持支持支持
块级标签内加空行解析 Markdown支持支持支持不支持不支持部分支持
markdown="1" 属性不支持不支持支持支持(需扩展)不支持不支持
<details>/<summary>支持支持支持支持支持支持
<img> 设置 width/height支持支持支持支持支持支持
<span style="color:">支持支持支持支持支持支持
<iframe>不支持不支持支持支持部分支持支持
<script>不支持不支持不支持不支持不支持不支持

数据来源:各平台官方文档 + Stack Overflow 高赞回答 + 实际测试。GitHub 的 HTML 白名单可在 github/markup 项目中查看。

几个值得注意的点:

  • GitHub 出于安全考虑,对可用的 HTML 标签有白名单限制。<script><iframe><style> 这些标签会被直接过滤掉。
  • Obsidian 内部使用自己的 Markdown 解析器,对 HTML 的支持有其特殊性。比如在 Obsidian 里不能在 HTML 块中使用 Markdown 语法,但可以通过 DataviewJS 插件间接实现。
  • Jekyll 的 Kramdown 解析器对 HTML in Markdown 的支持是最全面的,markdown="1" 属性和块级标签内加空行两种方式都支持。

实际应用场景与示例

场景一:GitHub README 中让图片居中

这是我在做开源项目时经常用的写法:

<div align="center">

![项目 Logo](logo.png)

**一个简洁的项目描述**

[![GitHub Stars](https://img.shields.io/github/stars/user/repo)](https://github.com/user/repo)

</div>

在 GitHub 上,这个写法能正常工作是因为 <div align="center"> 后面的空行让解析器把内容当作 Markdown 来处理。顺便说一句,<div align="center"> 虽然用了过时的 align 属性,但在 GitHub 上这是最可靠的居中方式。

场景二:在 Markdown 中嵌入复杂表格

Markdown 表格不支持跨行跨列(rowspan/colspan)。当你需要这种效果时,可以直接用 HTML 表格:

<table>
  <tr>
    <th>功能</th>
    <th>免费版</th>
    <th>专业版</th>
  </tr>
  <tr>
    <td rowspan="2">存储空间</td>
    <td>5GB</td>
    <td>100GB</td>
  </tr>
  <tr>
    <td colspan="2">可扩展至 1TB(仅专业版)</td>
  </tr>
</table>

场景三:用 CSS 实现多列布局

在支持 style 属性的平台上(比如自己的博客或文档站),可以用 CSS 实现简单的多列排版:

<div style="display: flex; gap: 20px;">

<div style="flex: 1;">

### 左列内容

- 要点一
- 要点二

</div>

<div style="flex: 1;">

### 右列内容

- 要点三
- 要点四

</div>

</div>

不过这种写法在 GitHub 上不一定生效,因为 GitHub 会过滤掉 style 属性中的部分 CSS 规则。

常见问题

Markdown 文件中能直接写 CSS 吗?

可以,但支持程度有限。你可以用 <style> 标签来定义样式,不过很多平台(特别是 GitHub)会把它过滤掉。在自己的博客或文档站点上通常没问题:

<style>
.custom-box {
  border: 1px solid #ddd;
  padding: 15px;
  border-radius: 5px;
}
</style>

<div class="custom-box">
这段文字会有自定义的边框和内边距。
</div>

为什么我的 HTML 标签在 GitHub 上不生效?

最常见的原因有两个:一是 GitHub 有 HTML 白名单,<script><iframe><form> 等标签会被直接移除;二是 GitHub 会过滤掉 style 属性中的部分 CSS(比如 positionz-index 等可能影响页面布局的属性)。如果你需要完全控制 HTML 和 CSS,建议使用 GitHub Pages 而不是直接在 README 中写。

<font> 标签还能用吗?

技术上可以,但不推荐。<font> 在 HTML5 中已被弃用(deprecated),虽然浏览器仍然支持渲染。建议使用 <span style="color: red;"> 的方式来替代。

安全注意事项

在 Markdown 中嵌入 HTML 时,安全性是一个需要注意的方面,特别是在用户可以提交 Markdown 内容的平台上。

XSS 风险

如果在 Markdown 中允许任意 HTML,就可能存在 XSS(跨站脚本攻击)的风险。比如:

<!-- 危险示例,不要在生产环境中使用 -->
<img src="x" onerror="alert('XSS')">
<a href="javascript:alert('XSS')">点击我</a>

大多数平台会过滤掉 onerroronclick 这类事件属性和 javascript: 协议的链接。但如果你自己在搭建 Markdown 渲染系统,需要确保使用 XSS 过滤库(比如 DOMPurify)来清理 HTML 内容。

内容安全策略

如果你在用 Markdown 解析库(如 marked.js、markdown-it)构建自己的应用,建议:

  1. 始终启用 HTML sanitization(HTML 清理)选项
  2. 使用白名单而非黑名单来过滤 HTML 标签和属性
  3. 不要信任用户提交的 Markdown 中的 HTML 内容

来源:OWASP XSS Prevention Cheat Sheet

总结

Markdown 与 HTML 的混合使用并不复杂,记住几个要点就够了:

  • 行内 HTML 标签<span><em><a> 等)可以在 Markdown 中随意使用,不影响 Markdown 语法解析
  • 块级 HTML 标签<div><table><p> 等)内部的 Markdown 语法默认不被解析
  • 如果需要在块级标签内使用 Markdown,可以尝试加空行(CommonMark 规范)或 markdown="1" 属性(Python-Markdown / Kramdown 支持)
  • 不同平台的 HTML 支持程度差异很大,使用前最好先在目标平台上测试
  • 安全性不能忽视,特别是在接受用户提交的 Markdown 内容时

说到底,Markdown 和 HTML 不是对立的。Markdown 处理大部分写作场景已经足够好了,偶尔需要更精细的控制时就用 HTML 来补充。两者配合使用,才是最实用的方式。

参考资料