latex公式渲染

目录

python渲染:

[linux 还需要安装](#linux 还需要安装)

python实时渲染代码:

js前端渲染

katex和mathjax比较

[1. 兼容性:MathJax 更胜一筹](#1. 兼容性:MathJax 更胜一筹)

[2. 纠错能力:MathJax 更加智能](#2. 纠错能力:MathJax 更加智能)

[3. 性能对比:KaTeX 遥遥领先](#3. 性能对比:KaTeX 遥遥领先)

总结与选型建议

katex公式渲染

mathjax公式实时渲染:

mathjax公式渲染示例


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 公式,例如:&#10;E = mc^2&#10;x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}&#10;\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>&lt;p&gt;勾股定理:$a^2 + b^2 = c^2$,这是行内公式。&lt;/p&gt;</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>
相关推荐
进击的雷神1 天前
蓝湖 MCP 快速上手手册(Claude + Codex)
arcgis·skill·蓝湖·mcp
非科班Java出身GISer1 天前
ArcGIS JS 基础教程(4):地图中心点定位(指定经纬度/地址)
arcgis·arcgis js定位·arcgis js地址定位·arcgis js参数定位·arcgis js复合定位
GIS地信小匠2 天前
(32)ArcGIS Pro WGS84坐标系:投影选择逻辑与实操设置
arcgis·空间分析·数据处理·gis教程·arcgls pro
玩大数据的龙威3 天前
农经权二轮延包—付费软件插件与免费软件插件汇总
python·arcgis
墨黎芜4 天前
ArcGIS从入门到精通——地图符号、注记的初步使用
学习·arcgis·信息可视化
GIS地信小匠5 天前
(31)ArcGIS Pro 定义投影与批量投影:矢量数据坐标转换工具实操
arcgis·空间分析·数据处理·gis教程·arcgls pro
非科班Java出身GISer5 天前
ArcGIS JS 基础教程(3):地图缩放、平移、旋转(基础交互)
arcgis·arcgis js地图交互·arcgis js缩放·arcgis js平移·arcgis js旋转·arcgis js基础交互
城数派5 天前
2025年我国省市县三级的平均坡度数据(Excel\Shp格式)
arcgis·信息可视化·数据分析·excel
装疯迷窍_A6 天前
以举证方位线生成工具为例,分享如何在Arcgis中创建Python工具箱(含源码)
开发语言·python·arcgis·变更调查·举证照片