When doing a React project, I want to render mathematical formulas on a webpage without using pre-packaged React components. I want to directly render the DOM using dynamic loading.
Mathjax@3 has made a major update, and the usage is different from version 2.x.
Dynamically Loading JS in React#
The first thing to know is how to dynamically load JS. You can mount a new script node to the DOM using JavaScript.
Create a script
element, set the src
attribute to the URL you want to load, and then append the node to the body element.
Here is a function that encapsulates this process. You just need to pass the URL of the JS file you want to load and use the .then
method for subsequent operations.
export const loadJS = (url: string) =>
new Promise(function (resolve, reject) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.body.appendChild(script);
script.onload = function () {
resolve(`success: ${url}`);
};
script.onerror = function () {
reject(Error(`${url} load error!`));
};
});
In the place where you need to use it:
loadJS(url)
.then(() => {
// do something
})
.catch(() => {
// do something
});
[email protected] Version#
First, here are some official reference links. This article may not be very clear.
- Loading and Configuring MathJax
https://docs.mathjax.org/en/v2.7-latest/configuration.html - Loading MathJax Dynamically
https://docs.mathjax.org/en/v2.7-latest/advanced/dynamic.html - Modifying Math on the Page
https://docs.mathjax.org/en/v2.7-latest/advanced/typeset.html
After loading Mathjax, we just need to render the DOM at the appropriate time.
Here's an example:
componentDidMount() {
const mathjaxUrl = 'https://cdn.bootcss.com/mathjax/2.7.4/MathJax.js?config=TeX-AMS_CHTML';
loadJS(mathjaxUrl).then(() => {
this.showMathjax();
});
}
componentDidUpdate () {
if (!(window as any).MathJax) {
(window as any).MathJax.Hub.Queue(['Typeset', (window as any).MathJax.Hub, ReactDOM.findDOMNode(this)]);
}
}
showMathjax = () => {
if ((window as any).MathJax) {
(window as any).MathJax.Hub.Config({
tex2jax: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
skipTags: ['script', 'noscript', 'style', 'textarea', 'code', 'a'],
},
CommonHTML: {
scale: 120,
linebreaks: { automatic: true },
},
'HTML-CSS': { linebreaks: { automatic: true } },
SVG: { linebreaks: { automatic: true } },
TeX: { noErrors: { disabled: true } },
});
} else {
setTimeout(this.showMathjax, 1000);
}
};
In version 2.x, we need to use Mathjax.Hub.Config
for configuration. The configurations are quite easy to understand. In tex2jax
, we set how to render mathematical formulas when they are wrapped in certain characters.
Inline formulas are wrapped in $...$
, and multiline formulas are wrapped in $$...$$
. We also specify which tags should not be rendered.
Then we set the rendering behavior for different modes. The JS file we loaded has a query ?config=TeX-AMS_CHTML
, which indicates that we loaded the CHTML configuration. Different configurations have different display effects. See https://docs.mathjax.org/en/v2.7-latest/config-files.html
After loading Mathjax
, it will automatically render the page. However, for single-page applications, Mathjax
will not re-render when the DOM is updated. We need to use MathJax.Hub.Queue(['Typeset', MathJax.Hub, ReactDOM.findDOMNode(this)]);
to manually render the DOM. Add it to componentDidUpdate
.
Mathjax@3 Version#
Here are the official documentation links:
- Configuring MathJax
http://docs.mathjax.org/en/v3.0-latest/options/index.html - MathJax in Dynamic Content
http://docs.mathjax.org/en/v3.0-latest/advanced/typeset.html
In version 3, the loading and configuration methods of Mathjax
are different.
Upgrading from v2 to v3: http://docs.mathjax.org/en/latest/upgrading/v2.html
You can directly load JS files with different configurations without using ?config=xxx
. For example, load https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
.
The Mathjax.Hub
methods have been removed. Now, you just need to assign a configuration object to window.Mathjax
to set the configuration.
The official documentation also provides a link to convert configurations: MathJax Configuration Converter
Here is my configuration:
(window as any).MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'code', 'a'],
},
chtml: {
scale: 1.2,
},
startup: {
ready: () => {
(window as any).MathJax.startup.defaultReady();
(window as any).MathJax.startup.promise.then(() => {
console.log('MathJax initial typesetting complete');
});
},
},
};
After the Mathjax script is loaded, it reads the window.Mathjax
as the configuration and replaces it with the Mathjax
object, so you can call related functions.
When Mathjax
is initialized, it calls the startup.ready
method in the configuration, where you can provide prompts or other configurations.
The same problem exists: Mathjax
does not re-render when the DOM is updated. You need to use the MathJax.typesetPromise()
method. Set it in componentDidUpdate
.
componentDidUpdate() {
const MathJax = (window as any).MathJax;
if (MathJax) {
MathJax.typesetPromise && MathJax.typesetPromise();
}
}
This method is asynchronous. There is also a synchronous method with the same functionality: MathJax.typeset()
.