Web 数学渲染引擎漫谈:MathJax 与 KaTeX¶
一句话结论:MathJax 和 KaTeX 是 Web 上最流行的两种 LaTeX 公式渲染方案,前者以兼容性与完整性取胜(支持几乎所有 TeX 语法),后者以速度和轻量著称;两者各有千秋,但都与
pymdownx.arithmatex这条 Python Markdown 生态链有微妙的耦合关系——选错组合会触发诡异的解析错误。
一、起源:两条不同的路¶
MathJax¶
MathJax 最初由 John Denker 于 2006 年发起,灵感来自一个简单的问题:为什么网页上的数学公式总是显示得那么糟糕?
早期的解决方案是把公式画成图片再嵌入 HTML——这能保证一致性的视觉效果,但带来了三个致命缺点:
- 图片无法缩放;
- 字号不随页面字体改变;
- 可访问性差(屏幕阅读器读不到公式内容)。
Denker 的思路是让浏览器直接用 CSS + SVG / HTML/CSS 来渲染公式——这要求实现一个完整的 TeX 排版引擎,工作量巨大。2008 年的第一版发布后,MathJax 逐渐演变成一个开源社区项目,核心贡献者包括 David Loeffler、Steven Wood 等人。2019 年发布的 v3 是一次彻底重写:改用 CHTML(CSS + HTML)作为默认输出格式(原来同时支持 SVG、Common HTML 和 Native MML),并引入了全新的加载架构——这让 MathJax v3 的体积从 2MB+ 降到了约 800KB(gzip),成为目前最适合 Web 的版本。
KaTeX¶
KaTeX 的故事则更短、更聚焦。它由 Kevin Masson 和 Alex Wolfe 在 Khan Academy(可汗学院)内部开发,2015 年作为开源项目首次对外发布。设计哲学很直接:不要试图兼容所有 LaTeX,只做用户真正需要的子集,并且要快。
Khan Academy 的核心产品是一个在线练习平台,每道题的题干都包含大量 LaTeX 公式——如果页面加载因为等公式渲染而卡顿,用户体验会大打折扣。于是他们选择:
- 用 同步渲染 代替异步等待;
- 只实现高频使用的命令(
\frac、\sqrt、\sum、矩阵环境等),砍掉冷门宏包; - 预编译 CSS 规则来加速常见表达式。
结果就是今天那个体积只有 MathJax 三分之一的轻量级引擎。KaTeX 至今仍在 Khan Academy 维护,最新版本是 v0.16.x(截至 2026 年)。
二、共性与差异¶
共同点¶
| 维度 | 说明 |
|---|---|
| 输入格式 | 两者都接受标准 LaTeX 数学符号 $\alpha$、$$\int_0^1 x\,dx$$ 作为输入 |
| 输出方式 | 最终都在 DOM 里生成 <span> / <div> 包裹的 HTML + CSS |
| CDN 分发 | 两者都有 jsDelivr / unpkg 等公共 CDN,可直接 <script src> 引用 |
| 配置机制 | 都通过全局变量(window.MathJax / window.__khtml 或全局 katex)控制行为 |
| 错误处理 | 解析失败时都会抛出错误并在页面上显示红色警告提示 |
核心差异¶
| 维度 | MathJax v3 | KaTeX v0.16 |
|---|---|---|
| 渲染时机 | 异步(typesetPromise() 返回 Promise) |
同步(renderToString() 立即返回 HTML 字符串) |
| LaTeX 覆盖 | 几乎 100%(含 TikZ、amsthm、mathtools 等) | 约 70%(不支持 TikZ,部分 amssymb 命令缺失) |
| 首屏速度 | 较慢(需要下载 font cache + typesetting) | 极快(通常 < 50ms) |
| SPA 兼容性 | 原生支持(异步重排) | 需手动调用 |
.arithmatex 兼容 |
✅ 内置(processArithmatex:true) |
❌ 无原生支持,需自行遍历 |
| 体积(gzip) | tex-chtml ≈ 800KB | katex.min.js ≈ 550KB + katex.min.css ≈ 17KB |
| 离线支持 | 支持打包本地资源 | 支持打包本地资源 |
| 适用场景 | 学术论文网站、全功能需求 | 教学平台、移动端优先、追求首屏性能 |
三、使用方式概览¶
MathJax 的基本用法¶
<!-- 1. 提前配置 -->
<script>
window.MathJax = {
tex: { inlineMath: [['\\(', '\\)']] },
options: { skipHtmlTags: ['script', 'noscript', 'style', 'textarea'] }
};
</script>
<!-- 2. 加载库(必须紧接配置之后) -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" async></script>
正文中写 \(e^{i\pi}+1=0\),MathJax 会在页面加载完毕后自动扫描并重绘。
KaTeX 的基本用法¶
<!-- 1. 引入样式与库 -->
<link rel="stylesheet" href="katex.min.css">
<script src="katex.min.js"></script>
<script defer src="auto-render.min.js"></script>
<!-- 2. 正文 -->
<p>$E=mc^2$ 是著名公式。</p>
<!-- 3. 初始化(也可手动调用 renderMathInElement) -->
<script>
renderMathInElement(document.body, { delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false }
]});
</script>
注意 KaTeX 的 auto-render 脚本会扫描原始文本节点中的裸 $...$——一旦被其他插件(如 pymdownx)包装进 <span class="arithmatex"> 里,它就找不到了。
四、与本项目的关系¶
这个项目(MOOC · CS 自学课程指南)使用 Zensical 构建,Markdown 处理依赖 pymdownx.arithmatex。以下是关键决策链路:
发生了什么¶
graph LR
A["$e$ in Markdown"] --> B[pymdownx.arithmatex generic=true]
B --> C["<span class='arithmatex'>\\(e\\)</span>"]
C --> D{Which renderer?}
D -->|"MathJax"| E["✅ processArithmatex: true"]
D -->|"KaTeX"| F["❌ 读到 \\(e\\),报错"]
pymdownx.arithmatex.generic = true 会把全文的所有 \(...\) 包裹替换成带 .arithmatex 类的 span/div,原始裸公式在 DOM 中已经不存在了。这意味着:
- KaTeX 的
auto-render找不到任何 \(...\) → 完全失效; - 即使我们自己遍历
.arithmatex节点,拿到的文本是\e\(转义后的原文),KaTeX 会尝试把\e当作 TeX 命令执行,导致ParseError: Can't use function '\(' in math mode; - MathJax v3 的
processArithmatex: true是唯一原生认识这种结构的渲染器。
本项目的具体配置¶
zensical.toml:
[[project.extra.polyfills]]
path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
async = true
extra_javascript = [
"javascripts/mathjax.js", # ← 放置 window.MathJax 配置
...
]
docs/javascripts/mathjax.js:
// 必须在加载 tex-chtml.js 之前设置
window.MathJax = {
tex: { processArithmatex: true }, // 关键开关
// ...
};
document$.subscribe(function () {
// Zensical 即时导航触发重排
MathJax.startup.promise.then(MathJax.typesetPromise);
});
为什么不用 KaTeX¶
在本项目的技术栈下(Zensical + pymdownx.arithmatex),切换成 KaTeX 有两种路径:
- 禁用 arithmatex:但这会让所有现有文档失去公式支持(pymdownx.arithmatex 是默认开启的,且本项目很多已有文件依赖它);
- 手动遍历 span 并剥掉
\(\)外壳:可行但不优雅,还需要额外处理\[\]显示模式判断逻辑,属于重复造轮子。
相比之下,MathJax 只需要一条配置就能无缝工作,而且对复杂公式(\mathbb{R}、\begin{pmatrix}、TikZ 等)的支持也更完整——适合知识型网站的定位。
五、选型建议¶
根据你的场景对号入座:
| 场景 | 推荐方案 |
|---|---|
| 学术/技术博客,公式种类多 | MathJax v3 |
| 教学平台,首屏性能敏感 | KaTeX |
| 使用 Sphinx / MkDocs + pymdownx | MathJax(via processArithmatex) |
| 纯静态 HTML,无预处理层 | KaTeX auto-render 或 MathJax 均可 |
| 需要 KaTeX 风格但想保留 .arithmatex | 手写预处理:把 \(...\) → \( ... \) 去掉外壳后再渲染 |