目录
[linux 还需要安装](#linux 还需要安装)
[1. 兼容性:MathJax 更胜一筹](#1. 兼容性:MathJax 更胜一筹)
[2. 纠错能力:MathJax 更加智能](#2. 纠错能力:MathJax 更加智能)
[3. 性能对比:KaTeX 遥遥领先](#3. 性能对比:KaTeX 遥遥领先)
python渲染:
bash
pip install playwright
playwright install chromium
linux 还需要安装
bash
sudo apt install libgbm1
python实时渲染代码:
python
import time
from playwright.sync_api import sync_playwright
class MathJaxRenderer:
def __init__(self):
self.p = sync_playwright().start()
self.browser = self.p.chromium.launch(headless=True)
self.page = self.browser.new_page()
def close(self):
self.browser.close()
self.p.stop()
def render(self, latex_string: str):
html = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
window.MathJax = {{
tex: {{
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']]
}}
}};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
</head>
<body>
<div id="formula">$$ {latex_string} $$</div>
</body>
</html>
"""
self.page.set_content(html)
# 等待 MathJax 渲染完成
self.page.wait_for_function("""
window.MathJax && MathJax.startup && MathJax.startup.promise
""")
# 提取错误信息
result = self.page.evaluate("""
() => {
const el = document.querySelector('#formula');
// MathJax v3 错误
const errorNode = el.querySelector('mjx-merror');
if (errorNode) {
return {
success: false,
error: errorNode.textContent
};
}
// 备用(旧版本)
const err2 = el.querySelector('.MathJax_Error');
if (err2) {
return {
success: false,
error: err2.textContent
};
}
return {
success: true,
error: null
};
}
""")
return result
# ===== 测试 =====
renderer = MathJaxRenderer()
start=time.time()
print(renderer.render(r"\frac{a}{b}"))
# {'success': True, 'error': None}
print('time1',time.time()-start)
start=time.time()
print(renderer.render(r"\ln(e^{a+b}})"))
print('time2',time.time()-start)
start=time.time()
print(renderer.render(r"\ln(e^{a+b}})"))
# {'success': False, 'error': 'Missing } inserted'}
print('time3',time.time()-start)
renderer.close()
js前端渲染
katex和mathjax比较
兼容性 和纠错能力 这两个维度,结论很清晰:在兼容性(广度)和容错性上,MathJax 全面优于 KaTeX。
这两个库的侧重点完全不同,具体对比如下:
1. 兼容性:MathJax 更胜一筹
-
更广的浏览器支持:MathJax 兼容 IE 9+ 等老旧浏览器,而 KaTeX 通常只支持较现代的浏览器。
-
更全的输入/输出格式 :MathJax 支持 LaTeX、MathML 和 AsciiMath 输入,输出支持 HTML、SVG 和 MathML ;KaTeX 主要聚焦于 LaTeX 输入和 HTML 输出。
-
更完善的语法支持 :MathJax 几乎完整支持 LaTeX 语法;KaTeX 仅支持核心语法,不支持
physics等扩展包。
2. 纠错能力:MathJax 更加智能
KaTeX 是"严格模式",一旦语法不标准(如括号不匹配),它会直接报错停止渲染;而 MathJax 会尽最大努力去理解和渲染有问题的公式,给出最佳结果。
3. 性能对比:KaTeX 遥遥领先
这是 KaTeX 的核心优势。它的设计目标就是快 。在处理大量复杂公式时,KaTeX 的渲染速度通常比 MathJax 快 3-5 倍。
总结与选型建议
| 对比维度 | MathJax | KaTeX |
|---|---|---|
| 渲染速度 | 较慢,适合公式量少但复杂的场景 | 极快,适合公式密集型页面 |
| 兼容性 | 更优,支持老旧浏览器和 MathML | 良好,适合现代浏览器环境 |
| 语法支持 | 非常全面,几乎完整 LaTeX 支持 | 聚焦常用语法,高级功能有限 |
| 纠错与渲染 | 更智能,能尽力渲染有瑕疵的代码 | 严格,语法不标准时容易报错 |
| 包体积 | 较大 (~600KB) | 轻量 (~75KB gzipped) |
选择建议:
-
首选 MathJax :如果你非常看重公式显示的准确性、兼容性和容错能力,尤其是对于复杂的学术文档、LaTeX 功底不深、或需要兼容 MathML 格式时。
-
选择 KaTeX :如果你的核心目标是极致的页面加载性能和渲染速度(如技术博客、在线教程),且公式语法相对标准、不需要复杂的 LaTeX 功能。
katex公式渲染
mathjax公式实时渲染:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LaTeX 公式实时渲染器</title>
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
packages: {'[+]': ['base', 'ams', 'newcommand']}
},
options: {
ignoreHtmlClass: 'tex2jax_ignore',
processHtmlClass: 'tex2jax_process'
},
startup: {
ready: () => {
console.log('MathJax 已就绪');
MathJax.startup.defaultReady();
}
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 900px;
margin: 30px auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 2em;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 0.95em;
}
.editor-section {
margin-bottom: 30px;
}
label {
display: block;
margin-bottom: 10px;
color: #555;
font-weight: 500;
}
textarea {
width: 100%;
min-height: 150px;
padding: 15px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 14px;
border: 2px solid #e0e0e0;
border-radius: 10px;
resize: vertical;
transition: border-color 0.3s;
}
textarea:focus {
outline: none;
border-color: #667eea;
}
.toolbar {
display: flex;
gap: 10px;
margin: 15px 0;
flex-wrap: wrap;
}
button {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
}
button:hover {
background: #5a67d8;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button.secondary {
background: #48bb78;
}
button.secondary:hover {
background: #38a169;
}
button.warning {
background: #ed8936;
}
button.warning:hover {
background: #dd6b20;
}
.shortcut-btn {
background: #e2e8f0;
color: #4a5568;
padding: 8px 12px;
font-size: 13px;
}
.shortcut-btn:hover {
background: #cbd5e0;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.preview-section {
margin-top: 30px;
}
.status-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
padding: 10px 15px;
background: #f7fafc;
border-radius: 10px;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
}
.status-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85em;
font-weight: 600;
}
.status-success {
background: #c6f6d5;
color: #22543d;
}
.status-error {
background: #fed7d7;
color: #742a2a;
}
.status-pending {
background: #fefcbf;
color: #744210;
}
.render-area {
min-height: 200px;
padding: 30px;
background: #f7fafc;
border-radius: 10px;
border: 2px dashed #cbd5e0;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5em;
overflow-x: auto;
}
.render-area:empty::before {
content: "✨ 输入公式后点击渲染";
color: #a0aec0;
font-size: 1em;
}
.error-message {
margin-top: 10px;
padding: 12px;
background: #fed7d7;
border-left: 4px solid #e53e3e;
border-radius: 8px;
color: #742a2a;
display: none;
}
.error-message.show {
display: block;
}
.char-count {
color: #718096;
font-size: 0.9em;
}
.example-section {
margin: 15px 0;
}
.example-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
</style>
</head>
<body>
<div class="container">
<h1>📐 LaTeX 公式实时渲染器</h1>
<div class="subtitle">输入 LaTeX 代码,实时查看渲染结果</div>
<div class="editor-section">
<label for="latexInput">✏️ LaTeX 公式编辑器</label>
<textarea
id="latexInput"
placeholder="输入 LaTeX 公式,例如: E = mc^2 x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} \int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}"
>E = mc^2</textarea>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span class="char-count" id="charCount">0 字符</span>
<div class="toolbar">
<button onclick="renderFormula()" id="renderBtn">🎨 渲染公式</button>
<button onclick="clearInput()" class="secondary">🗑️ 清空</button>
<button onclick="copyRendered()" class="warning">📋 复制代码</button>
</div>
</div>
<div class="example-section">
<label style="margin-top: 15px;">📌 快捷插入</label>
<div class="example-buttons">
<button onclick="insertAtCursor('\\frac{}{}')" class="shortcut-btn">分数 \frac</button>
<button onclick="insertAtCursor('\\sqrt{}')" class="shortcut-btn">根号 \sqrt</button>
<button onclick="insertAtCursor('\\int_{}^{}')" class="shortcut-btn">积分 \int</button>
<button onclick="insertAtCursor('\\sum_{}^{}')" class="shortcut-btn">求和 \sum</button>
<button onclick="insertAtCursor('\\alpha')" class="shortcut-btn">α</button>
<button onclick="insertAtCursor('\\beta')" class="shortcut-btn">β</button>
<button onclick="insertAtCursor('\\pi')" class="shortcut-btn">π</button>
<button onclick="insertAtCursor('\\infty')" class="shortcut-btn">∞</button>
<button onclick="insertAtCursor('\\pm')" class="shortcut-btn">±</button>
<button onclick="insertAtCursor('\\cdot')" class="shortcut-btn">·</button>
</div>
</div>
</div>
<div class="preview-section">
<div class="status-bar">
<div class="status-indicator">
<span>📊 渲染状态:</span>
<span class="status-badge status-pending" id="statusBadge">等待输入</span>
</div>
<span id="renderTime"></span>
</div>
<div class="render-area" id="renderArea"></div>
<div class="error-message" id="errorMessage"></div>
</div>
</div>
<script>
// 初始化
document.addEventListener('DOMContentLoaded', () => {
updateCharCount();
// 监听输入变化
document.getElementById('latexInput').addEventListener('input', updateCharCount);
// 支持快捷键 Ctrl+Enter 渲染
document.getElementById('latexInput').addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
renderFormula();
}
});
});
function updateCharCount() {
const input = document.getElementById('latexInput');
const count = input.value.length;
document.getElementById('charCount').textContent = `${count} 字符`;
}
async function renderFormula() {
const input = document.getElementById('latexInput').value.trim();
const renderArea = document.getElementById('renderArea');
const statusBadge = document.getElementById('statusBadge');
const errorMsg = document.getElementById('errorMessage');
const startTime = performance.now();
if (!input) {
statusBadge.textContent = '⚠️ 请输入公式';
statusBadge.className = 'status-badge status-pending';
renderArea.innerHTML = '';
return;
}
// 重置状态
statusBadge.textContent = '🔄 渲染中...';
statusBadge.className = 'status-badge status-pending';
errorMsg.classList.remove('show');
try {
// 预处理:如果是纯公式,自动包装为显示公式
let formulaToRender = input;
if (!input.includes('$$') && !input.includes('\\[')) {
formulaToRender = `$$${input}$$`;
}
renderArea.innerHTML = formulaToRender;
// 调用 MathJax 渲染
await MathJax.typesetPromise([renderArea]);
// 检查渲染结果
const hasError = renderArea.querySelector('.MathJax_ERROR, mjx-merror');
const endTime = performance.now();
const renderDuration = ((endTime - startTime) / 1000).toFixed(2);
if (hasError) {
// 渲染失败
const errorText = hasError.textContent || '公式语法错误';
statusBadge.textContent = '❌ 渲染失败';
statusBadge.className = 'status-badge status-error';
errorMsg.textContent = `错误详情:${errorText}`;
errorMsg.classList.add('show');
} else {
// 渲染成功
statusBadge.textContent = '✅ 渲染成功';
statusBadge.className = 'status-badge status-success';
document.getElementById('renderTime').textContent = `⏱️ ${renderDuration}秒`;
}
} catch (error) {
console.error('渲染异常:', error);
statusBadge.textContent = '❌ 渲染失败';
statusBadge.className = 'status-badge status-error';
errorMsg.textContent = `异常:${error.message}`;
errorMsg.classList.add('show');
}
}
function clearInput() {
document.getElementById('latexInput').value = '';
document.getElementById('renderArea').innerHTML = '';
document.getElementById('statusBadge').textContent = '等待输入';
document.getElementById('statusBadge').className = 'status-badge status-pending';
document.getElementById('errorMessage').classList.remove('show');
document.getElementById('renderTime').textContent = '';
updateCharCount();
}
function copyRendered() {
const input = document.getElementById('latexInput');
input.select();
document.execCommand('copy');
// 临时提示
const btn = event.target;
const originalText = btn.textContent;
btn.textContent = '✅ 已复制';
setTimeout(() => {
btn.textContent = originalText;
}, 1500);
}
function insertAtCursor(text) {
const textarea = document.getElementById('latexInput');
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const content = textarea.value;
textarea.value = content.substring(0, start) + text + content.substring(end);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
updateCharCount();
}
</script>
</body>
</html>
mathjax公式渲染示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MathJax 数学公式渲染示例</title>
<!-- 引入 MathJax 库 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.6/tex-chtml.js" id="MathJax-script" async></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #f5f5f5;
}
.card {
background-color: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
h2 {
color: #555;
border-bottom: 2px solid #ddd;
padding-bottom: 5px;
}
.formula-block {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
font-size: 1.1em;
}
.code {
background-color: #e9ecef;
padding: 10px;
border-radius: 5px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
overflow-x: auto;
}
</style>
</head>
<body>
<h1>📐 MathJax 数学公式渲染示例</h1>
<!-- 示例1:行内公式 -->
<div class="card">
<h2>1️⃣ 行内公式 (Inline Math)</h2>
<p>公式可以写在段落中间,比如勾股定理:$a^2 + b^2 = c^2$,这是行内公式。</p>
<p>质能方程:$E = mc^2$,揭示了质量和能量的关系。</p>
<div class="code">
<code><p>勾股定理:$a^2 + b^2 = c^2$,这是行内公式。</p></code>
</div>
</div>
<!-- 示例2:块级公式 -->
<div class="card">
<h2>2️⃣ 块级公式 (Display Math)</h2>
<p>下面是著名的求根公式,它会单独成行并居中显示:</p>
<div class="formula-block">
$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$
</div>
<p>还有欧拉恒等式:</p>
<div class="formula-block">
$$e^{i\pi} + 1 = 0$$
</div>
<div class="code">
<code>$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$</code>
</div>
</div>
<!-- 示例3:分数和根号 -->
<div class="card">
<h2>3️⃣ 分数和根号</h2>
<p>分数:$\frac{1}{2} + \frac{1}{3} = \frac{5}{6}$</p>
<p>根号:$\sqrt{2} \approx 1.414$,立方根:$\sqrt[3]{8} = 2$</p>
<p>嵌套公式:$\frac{\sqrt{x+1}}{\sqrt{x-1}}$</p>
</div>
<!-- 示例4:希腊字母和运算符 -->
<div class="card">
<h2>4️⃣ 希腊字母和运算符</h2>
<p>希腊字母:$\alpha, \beta, \gamma, \delta, \epsilon, \pi, \theta, \sigma, \omega$</p>
<p>求和:$\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$</p>
<p>积分:$\int_{0}^{\infty} e^{-x} dx = 1$</p>
<p>极限:$\lim_{x \to 0} \frac{\sin x}{x} = 1$</p>
</div>
<!-- 示例5:矩阵 -->
<div class="card">
<h2>5️⃣ 矩阵</h2>
<p>一个 $2 \times 2$ 矩阵:</p>
<div class="formula-block">
$$A = \begin{pmatrix} a & b \\ c & d \end{pmatrix}$$
</div>
<p>行列式:$\det(A) = ad - bc$</p>
</div>
<!-- 示例6:复杂公式 -->
<div class="card">
<h2>6️⃣ 复杂公式示例</h2>
<p>正态分布概率密度函数:</p>
<div class="formula-block">
$$f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}$$
</div>
<p>泰勒展开式:</p>
<div class="formula-block">
$$e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!} = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots$$
</div>
<p>傅里叶级数:</p>
<div class="formula-block">
$$f(x) = a_0 + \sum_{n=1}^{\infty} \left(a_n \cos\frac{n\pi x}{L} + b_n \sin\frac{n\pi x}{L}\right)$$
</div>
</div>
<!-- 示例7:多行公式 -->
<div class="card">
<h2>7️⃣ 多行对齐公式</h2>
<div class="formula-block">
$$
\begin{aligned}
(x+y)^2 &= x^2 + 2xy + y^2 \\
(x-y)^2 &= x^2 - 2xy + y^2 \\
x^2 - y^2 &= (x+y)(x-y)
\end{aligned}
$$
</div>
</div>
<!-- 示例8:你之前的例子 -->
<div class="card">
<h2>8️⃣ 你提到的例子</h2>
<p>原公式:$x_0 = x(t)$</p>
<div class="formula-block">
$$x_0 = x(t)$$
</div>
<p>绝对值不等式:$|a(t)| \le 1$</p>
<div class="formula-block">
$$|a(t)| \le 1$$
</div>
<p>区间表示:$\in [3, 7]$</p>
<div class="formula-block">
$$\in [3, 7]$$
</div>
</div>
<div class="card">
<h2>📝 使用说明</h2>
<ul>
<li><strong>行内公式</strong>:使用 <code>$...$</code> 或 <code>\(...\)</code></li>
<li><strong>块级公式</strong>:使用 <code>$$...$$</code> 或 <code>\[...\]</code></li>
<li><strong>常用命令</strong>:分式 <code>\frac{}{}</code>,根号 <code>\sqrt{}</code>,求和 <code>\sum</code>,积分 <code>\int</code></li>
<li><strong>上下标</strong>:上标 <code>^</code>,下标 <code>_</code></li>
</ul>
</div>
</body>
</html>
