Hopefully useful pieces of information or insight.
2022-01-30
KaTeX is the hottest JavaScript mathematical equation renderer nowadays, allowing anyone to add nice tex equations to their website by including a few JavaScript and CSS files. Most implementations of this however, render the math client-side, using JavaScript. Since the equations typically do not change at runtime, a faster and more efficient approach is to render the equations at website build time.
Here is the quadratic formula using KaTeX, rendered at build-time:
And here it is rendered client-side (using the Auto-render Extension): $$ \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$ They look identical but the client-side equation takes just a bit longer to hit your screen. Okay, maybe it's negligible for small equations, but let's try the Standard Model, courtesy of T.D. Gutierrez.
Try refreshing the page, and compare what happens below to the equation pre-rendered as HTML on the left, and the one rendered at page load time on the right. When I tried this, there was a short but noticeable delay in the rendering of the client-side equations, during which the LaTeX code used can be seen. It was a pleasant surprise that KaTeX is able to render this massive equation in under a second, so the user experience is not too bad, however. Credit to KaTeX for living up to its claim of fast equation rendering! Still, with many equations, the layout shifts on page-load can be jarring, and the build-time approach allows the entire page to remain JavaScript-free.
Build-time: |
Client-side: $$ \scriptsize -\frac{1}{2}\partial_{\nu}g^{a}_{\mu}\partial_{\nu}g^{a}_{\mu} -g_{s}f^{abc}\partial_{\mu}g^{a}_{\nu}g^{b}_{\mu}g^{c}_{\nu} -\frac{1}{4}g^{2}_{s}f^{abc}f^{ade}g^{b}_{\mu}g^{c}_{\nu}g^{d}_{\mu}g^{e}_{\nu} +\frac{1}{2}ig^{2}_{s}(\bar{q}^{\sigma}_{i}\gamma^{\mu}q^{\sigma}_{j})g^{a}_{\mu} +\bar{G}^{a}\partial^{2}G^{a}+g_{s}f^{abc}\partial_{\mu}\bar{G}^{a}G^{b}g^{c}_{\mu} -\partial_{\nu}W^{+}_{\mu}\partial_{\nu}W^{-}_{\mu}-M^{2}W^{+}_{\mu}W^{-}_{\mu} -\frac{1}{2}\partial_{\nu}Z^{0}_{\mu}\partial_{\nu}Z^{0}_{\mu}-\frac{1}{2c^{2}_{w}} M^{2}Z^{0}_{\mu}Z^{0}_{\mu} -\frac{1}{2}\partial_{\mu}A_{\nu}\partial_{\mu}A_{\nu} -\frac{1}{2}\partial_{\mu}H\partial_{\mu}H-\frac{1}{2}m^{2}_{h}H^{2} -\partial_{\mu}\phi^{+}\partial_{\mu}\phi^{-}-M^{2}\phi^{+}\phi^{-} -\frac{1}{2}\partial_{\mu}\phi^{0}\partial_{\mu}\phi^{0}-\frac{1}{2c^{2}_{w}}M\phi^{0}\phi^{0} -\beta_{h}[\frac{2M^{2}}{g^{2}}+\frac{2M}{g}H+\frac{1}{2}(H^{2}+\phi^{0}\phi^{0}+2\phi^{+}\phi^{-%%@ })]+\frac{2M^{4}}{g^{2}}\alpha_{h} -igc_{w}[\partial_{\nu}Z^{0}_{\mu}(W^{+}_{\mu}W^{-}_{\nu}-W^{+}_{\nu}W^{-}_{\mu}) -Z^{0}_{\nu}(W^{+}_{\mu}\partial_{\nu}W^{-}_{\mu}-W^{-}_{\mu}\partial_{\nu}W^{+}_{\mu}) +Z^{0}_{\mu}(W^{+}_{\nu}\partial_{\nu}W^{-}_{\mu}-W^{-}_{\nu}\partial_{\nu}W^{+}_{\mu})] -igs_{w}[\partial_{\nu}A_{\mu}(W^{+}_{\mu}W^{-}_{\nu}-W^{+}_{\nu}W^{-}_{\mu}) -A_{\nu}(W^{+}_{\mu}\partial_{\nu}W^{-}_{\mu}-W^{-}_{\mu}\partial_{\nu}W^{+}_{\mu}) +A_{\mu}(W^{+}_{\nu}\partial_{\nu}W^{-}_{\mu}-W^{-}_{\nu}\partial_{\nu}W^{+}_{\mu})] -\frac{1}{2}g^{2}W^{+}_{\mu}W^{-}_{\mu}W^{+}_{\nu}W^{-}_{\nu}+\frac{1}{2}g^{2} W^{+}_{\mu}W^{-}_{\nu}W^{+}_{\mu}W^{-}_{\nu} +g^2c^{2}_{w}(Z^{0}_{\mu}W^{+}_{\mu}Z^{0}_{\nu}W^{-}_{\nu}-Z^{0}_{\mu}Z^{0}_{\mu}W^{+}_{\nu} W^{-}_{\nu}) +g^2s^{2}_{w}(A_{\mu}W^{+}_{\mu}A_{\nu}W^{-}_{\nu}-A_{\mu}A_{\mu}W^{+}_{\nu} W^{-}_{\nu}) +g^{2}s_{w}c_{w}[A_{\mu}Z^{0}_{\nu}(W^{+}_{\mu}W^{-}_{\nu}-W^{+}_{\nu}W^{-}_{\mu})-%%@ 2A_{\mu}Z^{0}_{\mu}W^{+}_{\nu}W^{-}_{\nu}] -g\alpha[H^3+H\phi^{0}\phi^{0}+2H\phi^{+}\phi^{-}] -\frac{1}{8}g^{2}\alpha_{h}[H^4+(\phi^{0})^{4}+4(\phi^{+}\phi^{-})^{2}+4(\phi^{0})^{2} \phi^{+}\phi^{-}+4H^{2}\phi^{+}\phi^{-}+2(\phi^{0})^{2}H^{2}] -gMW^{+}_{\mu}W^{-}_{\mu}H-\frac{1}{2}g\frac{M}{c^{2}_{w}}Z^{0}_{\mu}Z^{0}_{\mu}H -\frac{1}{2}ig[W^{+}_{\mu}(\phi^{0}\partial_{\mu}\phi^{-}-\phi^{-}\partial_{\mu}\phi^{0}) -W^{-}_{\mu}(\phi^{0}\partial_{\mu}\phi^{+}-\phi^{+}\partial_{\mu}\phi^{0})] +\frac{1}{2}g[W^{+}_{\mu}(H\partial_{\mu}\phi^{-}-\phi^{-}\partial_{\mu}H) -W^{-}_{\mu}(H\partial_{\mu}\phi^{+}-\phi^{+}\partial_{\mu}H)] +\frac{1}{2}g\frac{1}{c_{w}}(Z^{0}_{\mu}(H\partial_{\mu}\phi^{0}-\phi^{0}\partial_{\mu}H) -ig\frac{s^{2}_{w}}{c_{w}}MZ^{0}_{\mu}(W^{+}_{\mu}\phi^{-}-W^{-}_{\mu}\phi^{+}) +igs_{w}MA_{\mu}(W^{+}_{\mu}\phi^{-}-W^{-}_{\mu}\phi^{+}) -ig\frac{1-2c^{2}_{w}}{2c_{w}}Z^{0}_{\mu}(\phi^{+}\partial_{\mu}\phi^{-}-\phi^{-%%@ }\partial_{\mu}\phi^{+}) +igs_{w}A_{\mu}(\phi^{+}\partial_{\mu}\phi^{-}-\phi^{-}\partial_{\mu}\phi^{+}) -\frac{1}{4}g^{2}W^{+}_{\mu}W^{-}_{\mu}[H^{2}+(\phi^{0})^{2}+2\phi^{+}\phi^{-}] -\frac{1}{4}g^{2}\frac{1}{c^{2}_{w}}Z^{0}_{\mu}Z^{0}_{\mu}[H^{2}+(\phi^{0})^{2}+2(2s^{2}_{w}-%%@ 1)^{2}\phi^{+}\phi^{-}] -\frac{1}{2}g^{2}\frac{s^{2}_{w}}{c_{w}}Z^{0}_{\mu}\phi^{0}(W^{+}_{\mu}\phi^{-}+W^{-%%@ }_{\mu}\phi^{+}) -\frac{1}{2}ig^{2}\frac{s^{2}_{w}}{c_{w}}Z^{0}_{\mu}H(W^{+}_{\mu}\phi^{-}-W^{-}_{\mu}\phi^{+}) +\frac{1}{2}g^{2}s_{w}A_{\mu}\phi^{0}(W^{+}_{\mu}\phi^{-}+W^{-}_{\mu}\phi^{+}) +\frac{1}{2}ig^{2}s_{w}A_{\mu}H(W^{+}_{\mu}\phi^{-}-W^{-}_{\mu}\phi^{+}) -g^{2}\frac{s_{w}}{c_{w}}(2c^{2}_{w}-1)Z^{0}_{\mu}A_{\mu}\phi^{+}\phi^{-}-%%@ g^{1}s^{2}_{w}A_{\mu}A_{\mu}\phi^{+}\phi^{-} -\bar{e}^{\lambda}(\gamma\partial+m^{\lambda}_{e})e^{\lambda} -\bar{\nu}^{\lambda}\gamma\partial\nu^{\lambda} -\bar{u}^{\lambda}_{j}(\gamma\partial+m^{\lambda}_{u})u^{\lambda}_{j} -\bar{d}^{\lambda}_{j}(\gamma\partial+m^{\lambda}_{d})d^{\lambda}_{j} +igs_{w}A_{\mu}[-(\bar{e}^{\lambda}\gamma^{\mu} e^{\lambda})+\frac{2}{3}(\bar{u}^{\lambda}_{j}\gamma^{\mu} %%@ u^{\lambda}_{j})-\frac{1}{3}(\bar{d}^{\lambda}_{j}\gamma^{\mu} d^{\lambda}_{j})] +\frac{ig}{4c_{w}}Z^{0}_{\mu} [(\bar{\nu}^{\lambda}\gamma^{\mu}(1+\gamma^{5})\nu^{\lambda})+ (\bar{e}^{\lambda}\gamma^{\mu}(4s^{2}_{w}-1-\gamma^{5})e^{\lambda})+ (\bar{u}^{\lambda}_{j}\gamma^{\mu}(\frac{4}{3}s^{2}_{w}-1-\gamma^{5})u^{\lambda}_{j})+ (\bar{d}^{\lambda}_{j}\gamma^{\mu}(1-\frac{8}{3}s^{2}_{w}-\gamma^{5})d^{\lambda}_{j})] +\frac{ig}{2\sqrt{2}}W^{+}_{\mu}[(\bar{\nu}^{\lambda}\gamma^{\mu}(1+\gamma^{5})e^{\lambda}) +(\bar{u}^{\lambda}_{j}\gamma^{\mu}(1+\gamma^{5})C_{\lambda\kappa}d^{\kappa}_{j})] +\frac{ig}{2\sqrt{2}}W^{-}_{\mu}[(\bar{e}^{\lambda}\gamma^{\mu}(1+\gamma^{5})\nu^{\lambda}) +(\bar{d}^{\kappa}_{j}C^{\dagger}_{\lambda\kappa}\gamma^{\mu}(1+\gamma^{5})u^{\lambda}_{j})] +\frac{ig}{2\sqrt{2}}\frac{m^{\lambda}_{e}}{M} [-\phi^{+}(\bar{\nu}^{\lambda}(1-\gamma^{5})e^{\lambda}) +\phi^{-}(\bar{e}^{\lambda}(1+\gamma^{5})\nu^{\lambda})] -\frac{g}{2}\frac{m^{\lambda}_{e}}{M}[H(\bar{e}^{\lambda}e^{\lambda}) +i\phi^{0}(\bar{e}^{\lambda}\gamma^{5}e^{\lambda})] +\frac{ig}{2M\sqrt{2}}\phi^{+} [-m^{\kappa}_{d}(\bar{u}^{\lambda}_{j}C_{\lambda\kappa}(1-\gamma^{5})d^{\kappa}_{j}) +m^{\lambda}_{u}(\bar{u}^{\lambda}_{j}C_{\lambda\kappa}(1+\gamma^{5})d^{\kappa}_{j}] +\frac{ig}{2M\sqrt{2}}\phi^{-} [m^{\lambda}_{d}(\bar{d}^{\lambda}_{j}C^{\dagger}_{\lambda\kappa}(1+\gamma^{5})u^{\kappa}_{j}) -m^{\kappa}_{u}(\bar{d}^{\lambda}_{j}C^{\dagger}_{\lambda\kappa}(1-\gamma^{5})u^{\kappa}_{j}] -\frac{g}{2}\frac{m^{\lambda}_{u}}{M}H(\bar{u}^{\lambda}_{j}u^{\lambda}_{j}) -\frac{g}{2}\frac{m^{\lambda}_{d}}{M}H(\bar{d}^{\lambda}_{j}d^{\lambda}_{j}) +\frac{ig}{2}\frac{m^{\lambda}_{u}}{M}\phi^{0}(\bar{u}^{\lambda}_{j}\gamma^{5}u^{\lambda}_{j}) -\frac{ig}{2}\frac{m^{\lambda}_{d}}{M}\phi^{0}(\bar{d}^{\lambda}_{j}\gamma^{5}d^{\lambda}_{j}) +\bar{X}^{+}(\partial^{2}-M^{2})X^{+}+\bar{X}^{-}(\partial^{2}-M^{2})X^{-} +\bar{X}^{0}(\partial^{2}-\frac{M^{2}}{c^{2}_{w}})X^{0}+\bar{Y}\partial^{2}Y +igc_{w}W^{+}_{\mu}(\partial_{\mu}\bar{X}^{0}X^{-}-\partial_{\mu}\bar{X}^{+}X^{0}) +igs_{w}W^{+}_{\mu}(\partial_{\mu}\bar{Y}X^{-}-\partial_{\mu}\bar{X}^{+}Y) +igc_{w}W^{-}_{\mu}(\partial_{\mu}\bar{X}^{-}X^{0}-\partial_{\mu}\bar{X}^{0}X^{+}) +igs_{w}W^{-}_{\mu}(\partial_{\mu}\bar{X}^{-}Y-\partial_{\mu}\bar{Y}X^{+}) +igc_{w}Z^{0}_{\mu}(\partial_{\mu}\bar{X}^{+}X^{+}-\partial_{\mu}\bar{X}^{-}X^{-}) +igs_{w}A_{\mu}(\partial_{\mu}\bar{X}^{+}X^{+}-\partial_{\mu}\bar{X}^{-}X^{-}) -\frac{1}{2}gM[\bar{X}^{+}X^{+}H+\bar{X}^{-}X^{-}H+\frac{1}{c^{2}_{w}}\bar{X}^{0}X^{0}H] +\frac{1-2c^{2}_{w}}{2c_{w}}igM[\bar{X}^{+}X^{0}\phi^{+}-\bar{X}^{-}X^{0}\phi^{-}] +\frac{1}{2c_{w}}igM[\bar{X}^{0}X^{-}\phi^{+}-\bar{X}^{0}X^{+}\phi^{-}] +igMs_{w}[\bar{X}^{0}X^{-}\phi^{+}-\bar{X}^{0}X^{+}\phi^{-}] +\frac{1}{2}igM[\bar{X}^{+}X^{+}\phi^{0}-\bar{X}^{-}X^{-}\phi^{0}] $$ |
To render equations during website build time,
I first installed KaTeX locally using npm install katex
.
I then use KaTeX's command line script, cli.js
, for every equation that needs to be rendered.
As an example, below is the webpage source for the first part of this web page.
Here is the quadratic formula using KaTeX, rendered at build-time:
{{ math \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}}}
A small Python script then replaces the "math" command with the output of running cli.js
with the LaTeX string.
Currently, this is accomplished using Python subprocess calls, but a more efficient implementation should call the renderToString
method directly.
from pathlib import Path
import subprocess
def htmlfromlatex(latex_s):
proc = subprocess.run(
["node", Path("node_modules")/"katex"/"cli.js", "-d"],
input=latex_s.encode('utf-8'),
capture_output=True
)
return proc.stdout.decode('utf-8').strip()
if __name__ == "__main__":
latex_s = "x^2"
print(htmlfromlatex(latex_s))
You should be able to run the above code after running npm install katex
, and see the outputted HTML for x^2.
You can also run the CLI interactively on the command-line by first typing some tex code, then sending the EOF signal with Ctrl + D.
The final HTML after "rendering" is quite unreadable and shown below for x^2 (you can also inspect the elements on this page).
<span class="katex">
<span class="katex-mathml">
<math xmlns="http://www.w3.org/1998/Math/MathML">
<semantics>
<mrow><msup><mi>x</mi><mn>2</mn></msup></mrow>
<annotation encoding="application/x-tex">x^2</annotation>
</semantics>
</math>
</span>
<span class="katex-html" aria-hidden="true">
<span class="base">
<span class="strut" style="height:0.8141em;"></span>
<span class="mord">
<span class="mord mathnormal">x</span>
<span class="msupsub">
<span class="vlist-t">
<span class="vlist-r">
<span class="vlist" style="height:0.8141em;">
<span style="top:-3.063em;margin-right:0.05em;">
<span class="pstrut" style="height:2.7em;"></span>
<span class="sizing reset-size6 size3 mtight">
<span class="mord mtight">2</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
Currently, I've gotten this working using the custom Python script static site generator. I imagine there are ways to do this with Jekyll, Hugo, and others, but for the moment, I am happy with this "custom, but easy to understand and extend" approach.
To display the rendered HTML properly, two things need to be available client-side: the KaTeX CSS file and the necessary fonts.
You can either copy katex.css
from the local KaTeX installation to somewhere in your deployment or retrieve it from some other server (e.g. see the Starter Template, but remember that the JavaScript files are not needed).
For the fonts, I've copied them all into the fonts/
folder at the root of the deployment, since that is how they are referenced from the CSS file.
Small note: remember to specify the charset of your HTML file as "utf-8" using a meta tag, since the rendered HTML uses unicode characters.
Happy KaTeXing!