MathJax 配置资产:xaringan 幻灯片

行内公式排坑、三方案对比,以及配对资产的拷贝与 YAML

xaringan
MathJax
JavaScript
R
Quarto
Author

Harness 技术文档

Published

October 13, 2026

摘要

xaringan(remark.js)对 $ 的识别偏保守,一行里第二个行内公式常常不排版,Quarto 式三行 $$ 也会落空。harness 在 js/mathjax-xaringan/ 维护推荐做法:纯 JavaScript 配置 + display-math 预处理,两份文件成对拷贝。

本文先说明原因与三种接法,再给出可复用资产的清单、YAML 与拷贝步骤。本地验证见 配置资产验证工作流 与 测试指引。

Quarto 网站与 Reveal.js 幻灯片使用另一套资产:MathJax 4 本地运行时(js/mathjax-quarto/)。那边自带组合组件,不使用本文的 tex2jax 配置,也不使用 wrap-display-math.R。


1. 为什么一行里只有第一个公式生效

典型句子是:当 $a=1$ 且 $b=2$ 时……往往只有 $a=1$ 被 MathJax 排版,后面仍是源码。remark.js 对 $ 的扫描较保守,又容易和下划线斜体抢解析。三行 $$ 块则根本进不了 MathJax 2 的 display 定界符,除非先改写成 remark 能保留的形式。

新课件默认用第 2.3 节的配对资产。单文件应急可以用第 2.1 节,需要 HTML 头注入时用第 2.2 节。


2. 三种接法

维度 直接嵌入 includes.in_header beforeInit + R hook(推荐)
harness 资产 无,把脚本写进 Rmd mathjax-config-inline-math-b.html mathjax-config.js + wrap-display-math.R
文件形态 带 <script> 的 HTML 带 <script> 的 HTML 纯 .js(不要包 HTML 标签)+ R
行间三行 $$ 不处理 视写法而定 支持(必须 source hook)
复用 每个 Rmd 复制一段 中 跨项目拷贝同一对文件
适用 临时单页 已有 HTML 头注入流程 新课程默认

2.1 直接嵌入

写在 YAML 之后、第一页幻灯片之前:

<script type="text/x-mathjax-config">
MathJax.Hub.Config({
  tex2jax: {
    inlineMath: [['$','$'], ['\\(','\\)']],
    processEscapes: true
  }
});
</script>

只修当前文件,且不管三行 $$。多门课不要靠复制这段 HTML。

2.2 includes.in_header

把带 <script> 的片段放到 mathjax-header.html(harness 中的对应文件是 mathjax-config-inline-math-b.html):

output:
  xaringan::moon_reader:
    includes:
      in_header: mathjax-header.html

in_header 的文件必须含 <script>。纯 .js 放在这里不会执行。

另有遗留文件 mathjax-config-inline-math.js:它用 MathJax 3 的 options.skipHtmlTags,与 xaringan 默认的 MathJax 2.x 不一致,不要用于新课件。

2.3 beforeInit 与预处理 hook

这是 harness 要拷进业务仓的一对文件。

组件 文件 作用 接入
MathJax 配置 mathjax-config.js 定界符、skipTags、AMS 扩展 YAML nature.beforeInit
Display-math 预处理 wrap-display-math.R 三行 $$ → `$$...$$` setup chunk source()

缺其中任一份,行间或行内公式都可能只修好一半。配置脚本不含 HTML 标签:

window.MathJax = {
  tex2jax: {
    inlineMath: [['$', '$'], ['\\(', '\\)']],
    displayMath: [['$$', '$$'], ['\\[', '\\]']],
    processEscapes: true,
    processEnvironments: true,
    skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
  },
  TeX: {
    equationNumbers: { autoNumber: 'none' },
    extensions: ['AMSmath.js', 'AMSsymbols.js']
  }
};

业务项目 YAML(beforeInit 相对该 Rmd):

output:
  xaringan::moon_reader:
    nature:
      beforeInit: "js/mathjax-config.js"

setup chunk(chunk 头写 {r setup, include=FALSE}):

source(here::here("slide-xaringan/Rscript/wrap-display-math.R"))
knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE)

路径按拷贝位置改。只写 beforeInit、不 source() hook,三行 $$ 仍然不渲染。


3. 拷贝到业务项目

这一对文件没有放进 Install-Harness.ps1,按 配置文件分发机制 手动拷贝。

slide-xaringan/
├── topic.Rmd
├── js/mathjax-config.js
├── Rscript/wrap-display-math.R
└── libs/
$HarnessRoot = "D:\github\harness"
$SlideDir    = "D:\path\to\slide-xaringan"

Copy-Item "$HarnessRoot\js\mathjax-xaringan\mathjax-config.js" "$SlideDir\js\mathjax-config.js" -Force
Copy-Item "$HarnessRoot\js\mathjax-xaringan\wrap-display-math.R" "$SlideDir\Rscript\wrap-display-math.R" -Force

课件里验证过的改动回灌 harness/js/mathjax-xaringan/,并更新本文。harness 升级后,两份文件一起重新拷贝或 diff 合并。

现象 可能原因
行间公式不渲染 未 source wrap-display-math.R
行内公式异常 未配置 beforeInit,或路径未指向 js/mathjax-config.js
只有部分 Rmd 正常 每个源文件都要成对配置
in_header 无效 注入的是纯 .js,缺少 <script>

4. 公式怎么写

remark.js 的限制与接法无关:

  1. 紧贴:$ 贴住公式首尾。$x+y$ 可以,$ x+y $ 不行。
  2. 转义下标:多个 _ 写成 \_,例如 $x\_i + y\_j$,避免被当成斜体。
  3. 不跨行:一个行内公式写在同一行。
  4. 备选定界符:$ 仍不稳时用 \( ... \)。配置里已经打开这组定界符。
  5. class 括号:.footnote[...]、.red[...]、.pull-left[...] 里,\(...\) 的反斜杠可能被 remark 吃掉,变成 inline code;$...$ 也可能失效。hook 修不了。公式放在幻灯片正文;必须写在 class 里时试 `\(...\)`。详见 测试指引 §4.4。

5. 验收

在 harness 内跑 js/mathjax-xaringan/test/:L0 单元,再按模块独立 R 进程做 L1 render。

在业务项目内:

  1. xaringan::inf_mr() 或 knit。
  2. 看 当 $a=1$ 且 $b=2$ 时 以及三行 $$ 是否都排版。
  3. 看 HTML <textarea id="source"> 里三行公式是否已变成 `$$...$$`。

6. 相关文档