浏览器里跑不了 TeX。因此把 LaTeX 数学公式放上网页 的工具——MathJax 与 KaTeX——都是用 JavaScript 重写的 TeX 数学排版,两者对同一个问题下了相反的赌注。在本机排同样的 500 个公式,KaTeX 用了 45 毫秒,MathJax 用了 264 毫秒。可只看速度选型,往往会在别处栽跟头:两者默认都不把 $...$ 当成数学公式。本页讨论两种设计的差别、你真正会看到的报错文字,以及粘进页面的 TeX 最后还在不在。
KaTeX 与 MathJax 的区别:该用哪一个
公式多、要速度就用 KaTeX;希望写下的 LaTeX 尽可能原样通过就用 MathJax。 速度差别不在于谁优化得更卖力,而在于 API 的形态。KaTeX 的 katex.renderToString(...) 同步返回字符串——调用当场就拿到 HTML,不会事后插入排版结果导致布局跳动。MathJax 在浏览器中则围绕 MathJax.typesetPromise() 设计,顾名思义返回一个 Promise。读者偶尔会瞥见一瞬未渲染的 \frac{1}{2},正是这种异步性的副作用。
<!-- MathJax 3: configure BEFORE the script tag loads -->
<script>
window.MathJax = {
tex: { inlineMath: [["$", "$"], ["\\(", "\\)"]] }
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<!-- KaTeX: stylesheet, engine, then the auto-render pass -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js"
onload="renderMathInElement(document.body);"></script>$...$ 为何不渲染:KaTeX 源码里的那行注释
两个库默认都不把 $...$ 当作行内公式。这不是疏忽,而是有意为之的默认值。打开 KaTeX auto-render 的源码,$ 那一条就在那里被注释掉了,理由写在紧上面一行:「LaTeX uses $…$, but it ruins the display of normal $ in text」。正文里出现两个货币符号 $,中间的内容就会被当成公式。MathJax 的默认值也一样:inlineMath 只有 \(...\),而 displayMath 是 $$...$$ 与 \[...\]。「公式以源码原样显示」这类求助,相当一部分就出在这一点上。
// KaTeX auto-render, default delimiters (dist/contrib/auto-render.js):
// { left: "$$", right: "$$", display: true }
// { left: "\\(", right: "\\)", display: false }
// { left: "\\[", right: "\\]", display: true }
// plus \begin{equation} \begin{align} \begin{alignat} \begin{gather} \begin{CD}
//
// and this line is deliberately commented out in the source:
// // LaTeX uses $...$, but it ruins the display of normal `$` in text:
// // {left: "$", right: "$", display: false},
// turn it on yourself only if the page has no currency amounts:
renderMathInElement(document.body, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
{ left: "\\(", right: "\\)", display: false },
{ left: "\\[", right: "\\]", display: true }
],
ignoredTags: ["script", "noscript", "style", "textarea", "pre", "code"]
});KaTeX 做不到的事,以及它打印的报错文字
KaTeX 支持的是 LaTeX 数学的一个子集,越界的内容会以异常抛出。措辞是固定的,形如 KaTeX parse error: Undefined control sequence: \eqref at position 1:。最致命的缺口是公式编号的交叉引用——\label 和 \eqref 都没有定义,于是要给公式编号并在正文中引用的文档,仅靠 KaTeX 就走不通了。化学式也过不去:\ce{H2O} 需要另行加载 mhchem 扩展。另一个常踩的坑是 KaTeX parse error: {align} can be used only in display mode.,在行内分隔符里写 align 环境时就会出现。
不过「KaTeX 不能用宏」是个误解。\newcommand、\def、\gdef 都可用;只要给 macros 选项传一个空对象,用 \gdef 定义的内容就会跨调用保留。全站通用的自定义宏,正是用这套机制一次性注入的。反过来看,MathJax 内置了约三十个 TeX 宏包的等价实现——ams、amscd、mathtools、mhchem、cancel、braket、bussproofs、empheq、colortbl 等等,这才是「兼容性更广」的实质。另外,若不想让一个坏公式毁掉整页,记住 throwOnError: false:KaTeX 不再抛出异常,而是把出错的源码用红色(#cc0000)原样显示出来。
| 你写的内容 | KaTeX | MathJax |
|---|---|---|
\frac \int \underbrace \text | 可用 | 可用 |
\newcommand \def \gdef | 可用(借 macros 保留) | 可用(macros 配置) |
\label \eqref | 不支持——Undefined control sequence | 可用(由 tags 选项编号) |
\ce{H2O} | 需另加 mhchem 扩展 | 已内置 |
align (inline) | {align} can be used only in display mode. | 可用 |
MathML 现在能用了吗:2023 年补上的缺口
主流浏览器都能显示 MathML。空缺最久的是 Chrome:支持加进去又撤掉,到版本 109 才正式回归(Edge 同样从 109 起)。Firefox 自版本 2 起就支持,Safari 自 10 起支持。话虽如此,实务中手写 MathML 的场合仍然不多。更要紧的是,两个库都把 MathML 放进了输出。KaTeX 总会在可见的 HTML 旁附上一个 <math> 元素;MathJax 的标准包 tex-mml-chtml.js 从一开始就加载了为辅助技术补充 MathML 的扩展。屏幕阅读器之所以读得出公式,正是靠这一隐藏层。
贴上页面的 TeX 还在吗:从哪一个能把公式复制回来
用 KaTeX 就还在;用 MathJax,默认不在。KaTeX 输出的 MathML 里必定有一个 <annotation encoding="application/x-tex">,里面原封不动地放着原始 TeX。喂给它 x^2+1,输出中某处就仍留着 x^2+1 这串字符。此外发行包里还附带一个 copy-tex 扩展,其源码注释写着「Replace .katex elements with their TeX source」——加载之后,选中公式复制,进入剪贴板的不是一串字形,而是 $x^2+1$。MathJax 常规的 HTML 输出里没有原始 TeX,它给的方案是右键菜单里的「Show Math As」,从那里取回原始写法。
<!-- what KaTeX puts in the DOM for x^2+1 -->
<span class="katex"><span class="katex-mathml"><math
xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow>
<msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><mn>1</mn>
</mrow><annotation encoding="application/x-tex">x^2+1</annotation>
</semantics></math></span><span class="katex-html" aria-hidden="true">...</span></span>
<!-- load this and Ctrl-C on a formula yields $x^2+1$, not glyphs -->
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/contrib/copy-tex.min.js"></script>把自定义宏送到浏览器
这里正是与「把整篇文档转成 HTML」那类工具的分界线。MathJax 和 KaTeX 收到的都只是数学片段,谁也不会读你的导言区。所以哪怕在 .tex 开头写了 \newcommand{\R}{\mathbb{R}},浏览器一侧也毫无所知,\R 会以未定义的红色出现。宏必须另行通过配置对象传入。转换工具那边也有同样的坑:make4ht 的 mathjax 模式把数学以 LaTeX 原样留在 HTML 中,因此自定义宏同样不会展开。转换工具的详情留给「LaTeX → HTML」,但对策是共通的——把宏定义集中在一处,让 TeX 和 JavaScript 都读它。
// KaTeX: one shared object, and \gdef survives from call to call
const macros = {};
katex.renderToString("\\gdef\\R{\\mathbb{R}}", { macros });
katex.renderToString("f\\colon \\R \\to \\R", { macros }); // \R resolves
// or declare them up front, the same way for the auto-render pass:
renderMathInElement(document.body, {
macros: { "\\R": "\\mathbb{R}", "\\eps": "\\varepsilon" },
throwOnError: false // print the bad source in red, do not break the page
});
// MathJax 3: the equivalent lives in the config object
window.MathJax = {
tex: {
macros: { R: "\\mathbb{R}", eps: "\\varepsilon" },
tags: "ams" // this is what enables \label and \eqref
}
};- 公式以源码原样显示 → 先怀疑分隔符。
$...$在 KaTeX 和 MathJax 中默认都是关闭的。 - 文档用到
\eqref→ 选 MathJax,并设置tags: "ams"。KaTeX 根本没有编号机制。 - 含数百个公式的列表页 → 选 KaTeX。同步渲染意味着版面不会事后跳动。
- 有自定义宏 → 一定要通过
macros传入。你的导言区到不了浏览器。 - 不想让一个坏公式毁掉整页 → KaTeX 用
throwOnError: false:以红字显示并继续。 - 想把整篇文档搬上网 → 那不是数学渲染器的活(见「LaTeX → HTML」)。