之前介紹過在 Markdown 可以直接用 LaTeX 語法寫數學公式,目前主流 Markdown 編輯軟體、套件或程式庫幾乎都有內建支援。我的部落格平台是自己開發維護,想支援只能自己來。

研究了一下,目前 LaTeX 主流 JavaScript 程式庫有兩套:MathJax 跟 KaTex,簡單總結二者差異:

  • MathJax - 程式庫肥大笨重,渲染速度慢,但支援完整 LaTeX 語法,且與 React、Vue、Angular 等框架整合性較好
  • KaTeX - 程式庫較輕巧,渲染速度快,但只支援部分 LaTeX 語法

KaTeX 官網有二者速度對比的動態展示,看起來挺驚悚的。我個人向來偏好簡單輕巧的解決方案,加上不會用到太複雜的語法,沒什麼懸念,就決定是 KaTeX 了。

要在既有網頁使用 KaTeX,只需引用 katex.min.css 及 katex.min.js,KaTeX 官網有針對在瀏覽器直接引用的完整說明,對我這類輕前端開發者很友善,葛萊分多加十分。

以下示範直接使用 CDN,加幾行程式生成數學公式:線上展示

<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.min.js"
        onload="renderMathInElement(document.body);"></script>
    <script>
    </script>
</head>

<body>
    <p>函式、根號 \(f(x) = \sqrt[3]{2x} + \sqrt{x-2}\),
    極限 \(\lim_{x \to 0^+} \dfrac{1}{x} = \infty\) (Inline 形式)
    </p>

    無窮級數 (Block 形式,使用 <code>\[ \]</code> 或 <code>$$ $$</code>)

    $$\mathrm{e} = \sum_{n=0}^{\infty} \dfrac{1}{n!}$$

    微分

    \[\frac{d}{dx} x^2 = 2x\]

    積分

    $$\int_a^b y \: \mathrm{d}x$$

    矩陣

    $$
    M =
    \begin{bmatrix}
    \frac{1}{2} & 0 \\
    0 & -1
    \end{bmatrix}
    \begin{bmatrix}
    1 & 0 \\
    0 & 1
    \end{bmatrix}
    $$

    矩陣(...)

    $$
    A_{m,n} =
    \begin{pmatrix}
    a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
    a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
    \vdots & \vdots & \ddots & \vdots \\
    a_{m,1} & a_{m,2} & \cdots & a_{m,n}
    \end{pmatrix}
    $$
</body>

</html>

官方範例使用 defer 非同步載入,再呼叫社群程式庫(auto-render.min.js)的 renderMathInElement() 掃描網頁中的 LaTeX 語法,自動轉成數學公式。識別依據是尋找用 $$...$$\[ ... \] 包夾的文字轉成區塊,用 \(...\)包夾的內容則會以 Inline 方式穿插在原有文字內容中。另一種常見的 Inline 標示法 $...$ 因為太容易誤判,預設不啟用。而掃描範圍會排除 script, noscript, style, textarea, pre, code, option 等元素。要完整掌握 renderMathInElement() 行為模式,最好的做法是看原始碼,所有的疑問馬上都能獲得解答:

const renderMathInElement = function(elem, options) {
    if (!elem) {
        throw new Error("No element provided to render");
    }

    const optionsCopy = {};

    // Object.assign(optionsCopy, option)
    for (const option in options) {
        if (options.hasOwnProperty(option)) {
            optionsCopy[option] = options[option];
        }
    }

    // default options
    optionsCopy.delimiters = optionsCopy.delimiters || [
        {left: "$$", right: "$$", display: true},
        {left: "\\(", right: "\\)", display: false},
        // LaTeX uses $…$, but it ruins the display of normal `$` in text:
        // {left: "$", right: "$", display: false},
        // $ must come after $$

        // Render AMS environments even if outside $$…$$ delimiters.
        {left: "\\begin{equation}", right: "\\end{equation}", display: true},
        {left: "\\begin{align}", right: "\\end{align}", display: true},
        {left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
        {left: "\\begin{gather}", right: "\\end{gather}", display: true},
        {left: "\\begin{CD}", right: "\\end{CD}", display: true},

        {left: "\\[", right: "\\]", display: true},
    ];
    optionsCopy.ignoredTags = optionsCopy.ignoredTags || [
        "script", "noscript", "style", "textarea", "pre", "code", "option",
    ];
    optionsCopy.ignoredClasses = optionsCopy.ignoredClasses || [];
    optionsCopy.errorCallback = optionsCopy.errorCallback || console.error;

    // Enable sharing of global macros defined via `\gdef` between different
    // math elements within a single call to `renderMathInElement`.
    optionsCopy.macros = optionsCopy.macros || {};

    renderElem(elem, optionsCopy);
};

由於我是在 Markdown 轉出結果套用 KaTeX,有機會透過特定語法格式控制轉換範圍,比自動掃描有效率並可避免誤判。我設計的做法是用 ` 或 ``` 包夾公式,配合 $ 及 $$ 標籤,範例如下:線上展示

<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
    <script>
        function parseCodeBlockLaTeX() {
            document.querySelectorAll('code').forEach((block) => {
                let tex = block.textContent.replace(/^\s+|\s+$/g, '');
                if (tex.match(/^\$\$.*\$\$$/s) || tex.match(/^\$.*[^$]\$$/s)) {
                    tex = tex.replace(/^\${1,2}|\${1,2}$/g, '');
                    let el = document.createElement('span');
                    katex.render(tex, el);
                    block.parentNode.replaceChild(el, block);
                }
            });
        }
        document.addEventListener('DOMContentLoaded', parseCodeBlockLaTeX);
    </script>
</head>

<body>
    <p>函式、根號 <code>$f(x) = \sqrt[3]{2x} + \sqrt{x-2}$</code>,
        極限 <code>$\lim_{x \to 0^+} \dfrac{1}{x} = \infty$</code>
    </p>
    <p>
        無窮級數 <code>$$\mathrm{e} = \sum_{n=0}^{\infty} \dfrac{1}{n!}$$</code>
    </p>
    <p>
        矩陣
    </p>
    <p>
        <code>
    $$
        M =
        \begin{bmatrix}
        \frac{1}{2} & 0 \\
        0 & -1
        \end{bmatrix}
        \begin{bmatrix}
        1 & 0 \\
        0 & 1
        \end{bmatrix}
    $$
    </code>
    </p>
    <p>
        不轉換
    </p>
    <p>
        <code>X$f(x)$</code>、<code>$f(x)$X</code>、<code>$$f(x)$</code>、<code>$f(x)$$</code>        
    </p>
</body>

</html>

掌握以上技巧,我們就能輕鬆在網頁顯示數學公式囉~

To support LaTeX in a custom Markdown blog, KaTeX is the preferred lightweight choice. Integrate it by including katex.min.css and katex.min.js from a CDN. Use renderMathInElement to auto-render LaTeX syntax, or customize with specific Markdown conventions for efficiency and accuracy.


Comments

Be the first to post a comment

Post a comment