1. 这不是“Markdown原生功能”,而是你必须搞懂的公式渲染链路
很多人第一次在 Markdown 编辑器里敲下$$E = mc^2$$,发现没反应,立刻去搜“Markdown 多行公式怎么写”,结果被一堆互相矛盾的答案绕晕:有人说用$$...$$,有人说必须装插件,有人贴出 KaTeX 配置代码却没说明放哪儿,还有人直接甩出一串 LaTeX 宏包加载命令——但你的编辑器根本连\usepackage{amsmath}都报错。这背后根本不是 Markdown 本身的问题,而是你混淆了“写作格式”和“渲染引擎”这两个完全不同的层级。
核心事实得先掰清楚:标准 CommonMark 规范里压根没有数学公式语法。Markdown 本质只是轻量级文本标记语言,它的设计哲学是“专注内容结构,不处理复杂排版”。公式、表格、流程图这些,全靠第三方渲染器在解析 Markdown 后的 HTML 阶段动态注入能力。所以当你问“用 Markdown 写多行公式”,真正要解决的其实是:如何让 Markdown 文件经过某套工具链后,在最终呈现(网页/PDF/预览窗)里正确显示对齐、编号、换行的数学表达式。
我做过三年技术文档平台架构,亲手对接过 7 种主流 Markdown 渲染方案,踩过的坑足够填满一个 LaTeX 宏包。最常被忽略的真相是:同一个$$\begin{aligned}...\end{aligned}$$块,在 VS Code 的 Markdown Preview、Typora、Obsidian、Jupyter Notebook、GitHub README 和自建 Hugo 博客里,表现可能天差地别——不是公式写错了,而是底层渲染器根本不支持你写的环境。比如 GitHub 用的是github-markup+kramdown,它只认$$...$$包裹的简单公式,遇到\begin{cases}直接当普通文本渲染;而 Obsidian 默认用 MathJax v3,对\begin{align*}支持极好,但若你启用了 KaTeX 插件,反而会因 KaTeX 对\intertext{}的兼容性问题导致编译失败。
关键词里反复出现的KaTeX和MathJax不是 Markdown 插件,而是独立的 JavaScript 渲染库。它们像两个不同品牌的“翻译官”:MathJax 是精通所有 LaTeX 方言的老教授,启动慢但兼容性无敌;KaTeX 是年轻高效的速记员,启动快但只认标准方言。你选哪个,直接决定你能用哪些多行公式语法。至于TeX Live,那是本地编译.tex文件的完整工具链,和 Markdown 渲染毫无关系——除非你走的是“Markdown → HTML → PDF”这种间接路径,否则装 TeX Live 对纯 Markdown 公式毫无帮助。那些搜“TeX Live 镜像下载”的人,八成是把 Markdown 公式和 LaTeX 排版混为一谈了。
所以别再问“Markdown 怎么写多行公式”,要问:“我的目标输出场景是什么?用什么工具预览?是否需要导出 PDF?团队协作时对方用什么编辑器?”——答案不同,解决方案天壤之别。接下来我会按实际工作流拆解:从最轻量的编辑器内联预览,到可导出 PDF 的专业方案,再到静态网站部署,每一步都告诉你为什么这么选、参数怎么调、哪里最容易翻车。
2. 编辑器内联预览:VS Code、Typora、Obsidian 的实操差异与避坑指南
2.1 VS Code:插件组合决定公式能力上限
VS Code 本身不渲染 Markdown,全靠插件。默认的Markdown Preview扩展只支持基础 HTML,公式需额外配置。最稳妥的组合是:Markdown All in One+Markdown Preview Enhanced(MPE)。前者管快捷键和目录,后者才是公式渲染核心。
MPE 默认用 KaTeX,但关键设置藏在settings.json里:
{ "markdown-preview-enhanced.katexMacros": [ "\\newcommand{\\R}{\\mathbb{R}}", "\\newcommand{\\N}{\\mathbb{N}}" ], "markdown-preview-enhanced.enableKaTeX": true, "markdown-preview-enhanced.mathRenderingOption": "katex" }注意mathRenderingOption必须显式设为"katex",否则可能 fallback 到 MathJax 导致速度变慢。KaTeX 对多行公式的支持有明确边界:它支持aligned,gather,multline环境,但不支持align*中的\intertext{}和\shortintertext{}。我曾为一个物理笔记写\begin{align*} F &= ma \\ \intertext{由牛顿第二定律得} a &= F/m \end{align*},结果预览里\intertext{}被当作文本渲染,整块公式错位。解决方案是改用aligned:
$$ \begin{aligned} F &= ma \\ \text{由牛顿第二定律得}\quad a &= F/m \end{aligned} $$这里用\text{}替代\intertext{},虽牺牲了自动间距,但保证了渲染稳定。另外 KaTeX 默认禁用\label{}和\ref{}交叉引用,若需编号引用,必须开启enableAutoNumbering并配合\tag{1}手动标号。
提示:VS Code 的
Ctrl+K V预览窗和右键“Open Preview to the Side”走的是同一套渲染逻辑,但若你同时装了Markdown Preview Mermaid Support,它可能劫持 MathJax 加载顺序,导致公式渲染失败。实测解决方案是禁用该插件,或在 MPE 设置中关闭enableMermaid。
2.2 Typora:开箱即用但版本陷阱
Typora 是少有的“所见即所得”Markdown 编辑器,公式支持堪称业界标杆。但它有个致命细节:0.11.18 版本前用 MathJax,之后全面切换 KaTeX。这意味着你在旧版写的\begin{equation}...\end{equation}自动编号,在新版会失效——KaTeX 不支持\begin{equation}环境,只认$$...$$或\\[...\\]包裹的aligned。
我帮客户迁移文档时发现,他们 2020 年存的.md文件里大量使用:
\begin{equation} \frac{d}{dx} \int_a^x f(t) dt = f(x) \end{equation}在 Typora 0.11.18+ 里直接显示为未渲染的 LaTeX 源码。修复只需两步:
- 将
\begin{equation}替换为$$; - 用
\tag{1}手动编号:
$$ \frac{d}{dx} \int_a^x f(t) dt = f(x) \tag{1} $$更隐蔽的坑是行内公式$...$里的\left\{和\right\}。KaTeX 要求左右括号必须在同一行,而 Typora 旧版允许跨行匹配。升级后若遇到Typeerror: miniprogramerror textencoder is not a constructor报错(这是 KaTeX 在 Web Worker 中初始化失败的典型错误),大概率是括号不匹配或用了 KaTeX 不支持的宏,如\DeclareMathOperator。此时应检查公式中是否有\operatorname{argmax}这类,KaTeX 仅支持\argmax内置命令,自定义需改用\text{argmax}。
2.3 Obsidian:插件生态决定自由度
Obsidian 的公式能力完全依赖社区插件。官方推荐MathJax Auto-Renderer,但实测稳定性不如Latex Suite。后者优势在于:
- 支持
\begin{cases}、\substack{}等复杂环境; - 可配置
autoNumber开关,开启后所有$$...$$块自动编号; - 提供
Ctrl+Shift+L快捷键插入常用模板,如aligned、matrix。
但 Latex Suite 有个隐藏限制:它默认将公式渲染为 SVG,而 SVG 在 Obsidian 的移动端 App 中缩放失真。解决方案是在插件设置里勾选Use MathJax instead of KaTeX,并指定 MathJax CDN 地址为https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js。这样虽牺牲一点加载速度,但确保跨平台一致性。
注意:Obsidian 的
Dataview插件与公式渲染存在冲突。若你在 Dataview 查询中嵌入公式(如$$\sum_{i=1}^n i = \frac{n(n+1)}{2}$$),查询结果页会报TypeError: Cannot read property 'textContent' of null。根本原因是 Dataview 动态生成 DOM 时,MathJax 尚未完成扫描。临时解法是在 Dataview 查询末尾加空行,或改用$$...$$包裹而非$...$行内模式。
3. 导出 PDF:Pandoc + LaTeX 的黄金组合与参数精调
3.1 为什么必须用 Pandoc?纯 Markdown 渲染器的硬伤
当你需要将 Markdown 文档导出为印刷级 PDF 时,所有基于浏览器的渲染方案(KaTeX/MathJax)都会失效——它们生成的是 HTML+CSS,而 PDF 导出本质是“重新排版”。这时唯一可靠路径是:Markdown → LaTeX → PDF。Pandoc 就是这个转换链的核心枢纽。
Pandoc 的强大在于它能把 Markdown 语法无缝映射到 LaTeX 命令。例如:
# 标题→\section{标题}$$E=mc^2$$→\begin{equation}E=mc^2\end{equation}- 但多行公式需特殊处理:Pandoc 默认将
$$\begin{aligned}...\end{aligned}$$当作普通块级元素,不识别其 LaTeX 环境,导致导出 PDF 时显示为乱码。
解决方案是启用--filter pandoc-crossref和--pdf-engine=xelatex,并在文档开头添加 YAML 元数据:
--- header-includes: - \usepackage{amsmath} - \usepackage{mathtools} - \usepackage{unicode-math} mainfont: "Noto Serif CJK SC" ---amsmath提供align,gather,multline等核心环境;mathtools是其增强版,支持\MoveEqLeft等高级对齐;unicode-math让 XeLaTeX 支持中文数学字体。没有这些,你的\begin{cases}会报错Undefined control sequence。
3.2 多行公式导出的三重校验法
我经手过 200+ 份学术报告 PDF 导出,总结出必做的三步校验:
第一步:源码级校验
在 Markdown 中写多行公式时,必须用 Pandoc 特定语法:
$$ \begin{align} \frac{\partial u}{\partial t} &= \alpha \nabla^2 u + f(x,y,t) \label{eq:heat} \\ u(x,y,0) &= u_0(x,y) \label{eq:init} \end{align} $$注意:
- 必须用
$$包裹,$...$行内模式在 PDF 导出中不生效; \label{}必须紧跟在\\换行符后,不能写在行首;\eqref{eq:heat}引用需在正文中用@eq:heat语法(Pandoc crossref 插件要求)。
第二步:中间文件校验
运行pandoc input.md -o temp.tex --standalone生成.tex文件,用文本编辑器打开,确认公式块已正确转为:
\begin{align} \frac{\partial u}{\partial t} &= \alpha \nabla^2 u + f(x,y,t) \label{eq:heat} \\ u(x,y,0) &= u_0(x,y) \label{eq:init} \end{align}若看到\begin{verbatim}...\end{verbatim}包裹的原始 LaTeX,则说明 Pandoc 未识别公式环境,需检查是否漏装amsmath或 YAML 元数据格式错误。
第三步:PDF 输出校验
用xelatex temp.tex编译,重点检查:
- 公式编号是否连续(
eq:heat显示为 (1),eq:init为 (2)); \eqref{eq:heat}是否正确显示为(1);- 中文公式变量(如
速度v)是否正常显示,若出现方框,需在 YAML 中添加\setmainfont{Noto Serif CJK SC}。
实操心得:Pandoc 默认用
pdflatex,但对中文支持极差。必须强制--pdf-engine=xelatex,且xelatex依赖系统字体。Windows 用户需安装Noto CJK Fonts,macOS 用户用brew install --cask font-noto-sans-cjk。若跳过此步,PDF 中所有中文数学符号会变成空白。
3.3 VS Code 一键导出工作流配置
在 VS Code 中实现Ctrl+Shift+P→ “Export to PDF” 一键操作,需配置任务(tasks.json):
{ "version": "2.0.0", "tasks": [ { "label": "Pandoc PDF Export", "type": "shell", "command": "pandoc", "args": [ "${file}", "-o", "${fileBasenameNoExtension}.pdf", "--pdf-engine=xelatex", "--template=custom.latex", "--highlight-style=pygments", "--toc", "--number-sections" ], "group": "build", "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": true } } ] }关键点:
--template=custom.latex指向自定义 LaTeX 模板,其中需包含amsmath加载;--highlight-style=pygments确保代码块高亮不破坏公式排版;--toc和--number-sections让目录与公式编号联动。
我自建的custom.latex模板中,数学相关部分如下:
% 数学宏包 \usepackage{amsmath} \usepackage{mathtools} \usepackage{unicode-math} \setmathfont{STIX Two Math} % 公式编号样式 \numberwithin{equation}{section} \renewcommand{\theequation}{\thesection.\arabic{equation}}这样导出的 PDF 中,第一章的公式编号为 (1.1)、(1.2),第二章为 (2.1),彻底解决编号混乱问题。
4. 静态网站部署:Hugo + KaTeX 的零配置方案与 CDN 优化
4.1 为什么 Hugo 是技术博客首选?
Hugo 是静态网站生成器中编译速度最快的(1000 篇文章 < 1 秒),其核心优势在于:所有 Markdown 渲染在构建时完成,无需客户端 JavaScript。这对公式渲染意味着:你可以在config.toml中全局启用 KaTeX,所有.md文件中的$$...$$块自动转换为<span class="katex">...</span>,用户访问时无需等待 MathJax 加载。
Hugo 的goldmark解析器原生支持数学扩展,只需在config.toml中添加:
[markup] [markup.goldmark] [markup.goldmark.renderer] unsafe = true [markup.math] enable = true engine = "katex" # KaTeX 配置 [markup.math.katex] cdn = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" js = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js" autoRender = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"注意unsafe = true是必需的,因为 KaTeX 渲染需插入<script>标签。autoRender脚本会自动扫描页面中的$$...$$和$...$并渲染,比手动调用renderMathInElement()更可靠。
4.2 多行公式在 Hugo 中的语法规范
Hugo 的数学扩展对 LaTeX 环境支持严格遵循 KaTeX 规则。以下写法全部有效:
<!-- align 环境,自动编号 --> $$ \begin{align} \int_0^\infty e^{-x^2} dx &= \frac{\sqrt{\pi}}{2} \label{eq:gauss} \\ \sum_{n=1}^\infty \frac{1}{n^2} &= \frac{\pi^2}{6} \label{eq:basel} \end{align} $$ <!-- cases 环境 --> $$ f(x) = \begin{cases} x^2 & x \geq 0 \\ -x & x < 0 \end{cases} $$ <!-- matrix 环境 --> $$ A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} $$但以下写法会失败:
\begin{equation*}:KaTeX 不支持星号环境,改用align*;\begin{array}{ccc}:KaTeX 仅支持matrix,pmatrix,bmatrix等预定义环境,array需手动声明列对齐,易出错;\tag{1}:Hugo 的 KaTeX 扩展会自动处理\label{},手动\tag{}可能覆盖自动编号。
4.3 CDN 加载失败的终极兜底方案
即使配置了 KaTeX CDN,用户网络波动仍可能导致公式渲染失败,页面显示原始 LaTeX 源码。我在生产环境部署过 37 个 Hugo 博客,总结出双保险策略:
第一层:CDN 备份
在config.toml中配置多个 CDN:
[markup.math.katex] cdn = [ "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css", "https://unpkg.com/katex@0.16.9/dist/katex.min.css" ] js = [ "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js", "https://unpkg.com/katex@0.16.9/dist/katex.min.js" ]第二层:本地 fallback
在layouts/partials/head.html中添加:
<script> // 检测 KaTeX 是否加载成功 if (typeof katex === 'undefined') { console.warn('KaTeX CDN failed, loading local copy'); document.write('<link rel="stylesheet" href="/css/katex.min.css">'); document.write('<script src="/js/katex.min.js"><\/script>'); } </script>并将katex.min.js和katex.min.css下载到static/css/和static/js/目录。这样即使 CDN 全挂,用户仍能看到公式。
关键经验:Hugo 构建时不会处理
static/目录外的资源。所有 KaTeX 相关文件必须放在static/下,且路径需与 HTML 中引用一致。我曾因把katex.min.js放在assets/js/导致 fallback 失效,调试 3 小时才发现 Hugo 的静态资源规则。
5. 常见问题与排查技巧实录:从报错信息反推故障根源
5.1 公式显示为原始 LaTeX 源码的 5 类原因及速查表
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
$$...$$块显示为$$E=mc^2$$ | 渲染器未启用数学扩展 | 检查编辑器插件是否启用;Hugo 中markup.math.enable = true | VS Code 启用 MPE;Hugo 配置enable = true |
$E=mc^2$行内公式不渲染 | 行内公式语法被禁用 | 查看渲染器文档,确认是否支持$...$ | KaTeX 默认禁用,需在配置中设inlineMath: [['$', '$']] |
多行公式换行符\\显示为文字 | LaTeX 环境未被识别 | 检查是否用$$包裹,而非$;确认环境名拼写正确 | 改用$$\begin{aligned}...\end{aligned}$$,避免\begin{align*} |
| 公式编号缺失或重复 | 自动编号未开启或\label{}位置错误 | 检查 YAML 元数据中autoNumber设置;确认\label{}在\\后 | Hugo 中设autoNumber = true;Pandoc 中\label{}紧跟\\ |
| 中文公式变量显示方框 | 字体未正确加载 | 运行fc-list | grep "Noto"检查系统字体 | Pandoc 中指定--pdf-engine=xelatex和mainfont |
5.2TypeError: TextEncoder is not a constructor深度解析
这个错误在微信小程序、旧版 Safari 或某些 Electron 应用中高频出现,根本原因是:KaTeX 0.13+ 版本依赖TextEncoderAPI,而该 API 在 Node.js < 11 或浏览器 < Chrome 54 中不可用。
排查路径:
- 打开浏览器开发者工具 → Console,复制完整错误栈;
- 若含
node_modules/katex/dist/katex.min.js,确认 KaTeX 版本(npm list katex); - 若版本 ≥ 0.13,且运行环境为旧版 Electron(如 Typora 0.10.x),则必现此错。
解决方案分三级:
- 一级(推荐):降级 KaTeX 到 0.12.0,它不依赖
TextEncoder; - 二级:在项目入口处 polyfill:
if (typeof TextEncoder === 'undefined') { global.TextEncoder = require('text-encoding').TextEncoder; } - 三级(治本):升级运行环境,如 Typora 更新到 0.11.18+,VS Code 使用最新版。
我曾为一个金融客户修复此问题,他们用 Electron 8 封装的内部工具,KaTeX 0.16.9 死活不工作。最终方案是:在preload.js中注入 polyfill,并将 KaTeX 版本锁定在 0.12.0,同时禁用所有 KaTeX 0.13+ 新特性(如\cancel命令)。
5.3 公式对齐错位的视觉调试法
当aligned环境中公式未按&对齐,不要盲目改代码。用浏览器开发者工具执行以下调试:
- 右键公式 → “检查元素”,找到
<span class="katex">; - 在 Styles 面板中,临时关闭
display: inline-block,观察是否恢复正常; - 若恢复正常,说明父容器 CSS 干扰(如
text-align: center); - 添加自定义 CSS:
.katex { display: inline-block !important; vertical-align: middle; }
更隐蔽的问题是行高(line-height)。KaTeX 渲染的公式默认line-height: 0,若父容器line-height: 1.5,会导致上下留白过大。解决方案是在 Hugo 的assets/css/custom.css中:
.katex-html { line-height: 1.2 !important; }5.4 从 LaTeX 错误日志反推 Markdown 源码缺陷
Pandoc 导出 PDF 失败时,终端会输出类似:
! Undefined control sequence. l.123 \begin{align}这表示第 123 行的\begin{align}未被识别。但 Markdown 源码中根本没有行号!此时需:
- 运行
pandoc input.md -o debug.tex生成中间文件; - 用
vim debug.tex打开,搜索\begin{align},定位到实际行号; - 回溯 Markdown 源码,找到对应段落——通常是公式块前后有空行缺失或多余空格。
经典案例:Markdown 中写:
文本内容。 $$ \begin{align} a &= b \\ c &= d \end{align} $$ 更多文本。Pandoc 会将$$块解析为独立段落,但若$$前后空行数不对(如$$前有 2 个空行),Pandoc 可能将其误判为代码块。解决方案:确保$$前后各 1 个空行,且$$与公式环境间无空行。
最后分享个小技巧:在 VS Code 中安装LaTeX Workshop插件,它能实时高亮 LaTeX 语法错误。虽然它是为.tex文件设计,但开启latex-workshop.latex.autoBuild.onSave.enabled后,对 Markdown 中的公式块同样有效——写错\end{align}时,编辑器会立即标红提示,比等 Pandoc 报错高效十倍。