ブラウザで TeX は動きません。ですから LaTeX の数式を Web ページに 出す道具——MathJax と KaTeX——は、どちらも TeX の数式組版を JavaScript で書き直したものです。同じ問題に対して二つは正反対の賭けをしました。手元で同じ数式を 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} も素の KaTeX では通らず、mhchem 拡張を別に読み込む必要があります。もう一つよく踏むのが KaTeX parse error: {align} can be used only in display mode. で、行内の区切り記号のなかに align 環境を書くと出ます。
ただし「KaTeX はマクロが使えない」というのは誤解です。\newcommand・\def・\gdef はいずれも通り、macros オプションに空のオブジェクトを渡しておけば \gdef した定義が 呼び出しをまたいで残ります。サイト全体で使う自作マクロは、この仕組みで一度だけ流し込むのが定石です。逆に MathJax は、TeX パッケージ相当の実装を 30 ほど内蔵しています——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 も受け取るのは数式の断片だけで、あなたのプリアンブルは読みません。だから \newcommand{\R}{\mathbb{R}} を .tex の冒頭に書いてあっても、ブラウザ側には何も伝わらず、\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。赤字で表示して先へ進みます。 - 文書まるごと Web にしたい → これは数式レンダラの仕事ではありません(→「LaTeX → HTML」)。