mathjax是一个用于latex、mathml和ascimath表示法的开源javascript显示引擎。mathjax的3.0版是对mathjax的彻底重写,实现了组件化,可以实现不同需求的定制,使用和配置与mathjax2版本有很大的不同,所以一定要注意版本。
最近在重构一个项目时,新增了一个需求支持latex数学公式渲染和编辑。在经过一番调研对比后,目前浏览器兼容性比较好的有两个,分别是KateX和MathJax。
性能对比截图
MathJax3
MathJax2.7
KaTex
从对比中可以看出MathJax版本2和3都是用使用 tex-chtml ,但之间的性能差距孩挺大的。Katex的渲染性能会比MathJax3好一些,但复杂公式的渲染效果不如MathJax,实际使用差别不大。最终选择了MathJax3,其中很大的主导因素是一个有项目依赖关系的兄弟部门里面已经在使用。
Vue中的使用
一般接触到一些新知识点,笔者都会先想办法借鉴一下大佬们的使用经验,上github看一下开源项目。
后面的内容可能对justforuse老哥不太友好,但还得感谢老哥提供的思路。所以开始之前得先强调一下,所有的开源项目和作者的努力及成果都应该得到尊重,后面的内容只是想陈述一下我看到的和为什么自己造了个轮子。
vue-mathjax的用法
主要代码:
export default {
watch: {
formula () {
this.renderMathJax()
mounted () {
this.renderMathJax()
methods: {
renderContent () {
if (this.safe) {
this.$refs.mathJaxEl.textContent = this.formula
} else {
this.$refs.mathJaxEl.innerHTML = this.formula
renderMathJax () {
this.renderContent()
if (window.MathJax) {
window.MathJax.Hub.Config({
window.MathJax.Hub.Queue([
'Typeset',
看完整个项目之后,产生了几个疑问🤔️
通过script标签在head引入,我想按需使用咋办?
mathjax加载完成后会默认把整个document.body渲染一遍产生多少开销?会不会导致不必要的错误渲染?
组件每次渲染的时候都执行一遍window.MathJax.Hub.Config?
一段文字里面有很多公式时,每个都要去改造成组件太麻烦还不美观。
mathjax2和3版本的性能差距。
总体上看这个项目是不符合我的需求的,尤其是对于大页面来说,问题2和3绝对是会带来性能问题,个人猜测问题3作者的出发点是为了让每个组件都可以支持不同配置,可关键点是作者代码没处理好,埋藏了性能问题。
开始造轮子
export function injectMathJax() {
if (!window.MathJax) {
const script = document.createElement('script')
script.src =
'https://cdn.bootcdn.net/ajax/libs/mathjax/3.2.0/es5/tex-chtml.js'
script.async = true
document.head.appendChild(script)
加载并配置MathJax 参考:MathJax
* 初始化 MathJax
* @param callback Mathjax加载完成后的回调
export function initMathJax(callback) {
injectMathJax()
window.MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
processEnvironments: true,
processRefs: true,
options: {
skipHtmlTags: ['noscript', 'style', 'textarea', 'pre', 'code'],
ignoreHtmlClass: 'tex2jax_ignore',
startup: {
pageReady: () => {
callback && callback()
svg: {
fontCache: 'global',
注:pageReady函数从性能方面考虑最好是自己配置,不配置会执行默认的自动渲染函数,遍历渲染整个document.body,导致不必要的性能开销(问题2)。
手动渲染某个Dom元素或集合,不需要改造成组件(问题4)
* 手动渲染公式 返回 Promise
* @param el 需要渲染的DOM元素或集合
* @returns Promise
export function renderByMathjax(el) {
if (!window.MathJax.version) {
return
if (el && !Array.isArray(el)) {
el = [el]
return new Promise((resolve, reject) => {
window.MathJax.typesetPromise(el)
.then(() => {
resolve(void 0)
.catch((err) => reject(err))
注:这里只是为了配合vue里面使用,传参可以改造成传一个选择器,然后renderByMathjax里面用document.querySelectorAll,就无需判断数组,调用也简洁方便。
function onMathJaxReady() {
const el = document.getElementById('elementId')
renderByMathjax(el)
initMathJax(onMathJaxReady)
renderByMathjax(document.getElementByClassNAme('class1'))
到这里已经支持在各种前端项目引入使用,部分可能需要改动一下,例如html中引入不支持ES6语法。
Vue组件(不存在问题3)
<template>
<span></span>
</template>
<script>
import { renderByMathjax } from '../utils'
export default {
name: 'MathJax',
props: {
latex: {
type: String,
default: '',
block: {
type: Boolean,
default: false,
watch: {
latex() {
this.renderMathJax()
mounted() {
this.renderMathJax()
methods: {
renderMathJax() {
this.$el.innerText = this.block
? `$$ ${this.latex} $$`
: `$ ${this.latex} $`
renderByMathjax(this.$el)
</script>
基于上面的示例代码,笔者发布了mathjax-vue和mathjax-vue3插件。
mathjax-vue用法
npm i --save mathjax-vue
yarn add mathjax-vue
import MathJax, { initMathJax, renderByMathjax } from 'mathjax-vue'
function onMathJaxReady() {
const el = document.getElementById('elementId')
renderByMathjax(el)
initMathJax({}, onMathJaxReady)
Vue.use(MathJax)
createApp(App).use(MathJax)
import { MathJax } from 'mathjax-vue'
export default {
components: {
MathJax,
不想插入组件
import { renderByMathjax } from 'mathjax-vue'
renderByMathjax(document.getElementById('id1'))
最后说一段题外话,笔者最近在准备开源一个数学公式编辑器,主要也是目前开源的数学公式编辑器无法满足业务需求,有需要的可以关注一下。如果本文有帮助到你,动动你的小手点个赞呗~
在线Demo:CodeSandbox
项目仓库:github