MathJax 配置资产:xaringan 幻灯片
行内公式排坑、三方案对比,以及配对资产的拷贝与 YAML
摘要
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.htmlin_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 的限制与接法无关:
- 紧贴:
$贴住公式首尾。$x+y$可以,$ x+y $不行。 - 转义下标:多个
_写成\_,例如$x\_i + y\_j$,避免被当成斜体。 - 不跨行:一个行内公式写在同一行。
- 备选定界符:
$仍不稳时用\( ... \)。配置里已经打开这组定界符。 - class 括号:
.footnote[...]、.red[...]、.pull-left[...]里,\(...\)的反斜杠可能被 remark 吃掉,变成 inline code;$...$也可能失效。hook 修不了。公式放在幻灯片正文;必须写在 class 里时试`\(...\)`。详见 测试指引 §4.4。
5. 验收
在 harness 内跑 js/mathjax-xaringan/test/:L0 单元,再按模块独立 R 进程做 L1 render。
在业务项目内:
xaringan::inf_mr()或 knit。- 看
当 $a=1$ 且 $b=2$ 时以及三行$$是否都排版。 - 看 HTML
<textarea id="source">里三行公式是否已变成`$$...$$`。