使用MathJax 3 渲染数学公式及在Vue中的使用

mathjax是一个用于latex、mathml和ascimath表示法的开源javascript显示引擎。mathjax的3.0版是对mathjax的完全重写,实现了组件化,能够实现不一样需求的定制,使用和配置与mathjax2版本有很大的不一样,因此必定要注意版本。javascript

最近在重构一个项目时,新增了一个需求支持latex数学公式渲染和编辑。在通过一番调研对比后,目前浏览器兼容性比较好的有两个,分别是KateX和MathJax。html

性能对比截图

MathJax3 在这里插入图片描述前端

MathJax2.7 在这里插入图片描述vue

KaTex 在这里插入图片描述java

从对比中能够看出MathJax版本2和3都是用使用 tex-chtml ,但之间的性能差距孩挺大的。Katex的渲染性能会比MathJax3好一些,但复杂公式的渲染效果不如MathJax,实际使用差异不大。最终选择了MathJax3,其中很大的主导因素是一个有项目依赖关系的兄弟部门里面已经在使用。git

Vue中的使用

通常接触到一些新知识点,笔者都会先想办法借鉴一下大佬们的使用经验,上github看一下开源项目。 在这里插入图片描述 后面的内容可能对justforuse老哥不太友好,但还得感谢老哥提供的思路。因此开始以前得先强调一下,全部的开源项目和做者的努力及成果都应该获得尊重,后面的内容只是想陈述一下我看到的和为何本身造了个轮子。github

vue-mathjax的用法 在这里插入图片描述 主要代码:ajax

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',
          // ...
        ])
      }
    }
  }
}
复制代码

看完整个项目以后,产生了几个疑问🤔️npm

  1. 经过script标签在head引入,我想按需使用咋办?
  2. mathjax加载完成后会默认把整个document.body渲染一遍产生多少开销?会不会致使没必要要的错误渲染?
  3. 组件每次渲染的时候都执行一遍window.MathJax.Hub.Config?
  4. 一段文字里面有不少公式时,每一个都要去改形成组件太麻烦还不美观。
  5. mathjax2和3版本的性能差距。

整体上看这个项目是不符合个人需求的,尤为是对于大页面来讲,问题2和3绝对是会带来性能问题,我的猜想问题3做者的出发点是为了让每一个组件均可以支持不一样配置,可关键点是做者代码没处理好,埋藏了性能问题。数组

开始造轮子

按需引入

// Mathjax to be injected into the document head
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'],
      // 跳过mathjax处理的元素的类名,任何元素指定一个类 tex2jax_ignore 将被跳过,多个累=类名'class1|class2'
      ignoreHtmlClass: 'tex2jax_ignore',
    },
    startup: {
      // 当mathjax加载并初始化完成后的回调
      pageReady: () => {
        callback && callback()
      },
    },
    svg: {
      fontCache: 'global',
    },
  }
}
复制代码

注:pageReady函数从性能方面考虑最好是本身配置,不配置会执行默认的自动渲染函数,遍历渲染整个document.body,致使没必要要的性能开销(问题2)。

手动渲染某个Dom元素或集合,不须要改形成组件(问题4)

/** * 手动渲染公式 返回 Promise * @param el 须要渲染的DOM元素或集合 * @returns Promise */
export function renderByMathjax(el) {
  // mathjax初始化后才会注入version
  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() {
  // 根据id渲染
  const el = document.getElementById('elementId')
  renderByMathjax(el)
}

initMathJax(onMathJaxReady)
// 根据class渲染
renderByMathjax(document.getElementByClassNAme('class1'))
复制代码

到这里已经支持在各类前端项目引入使用,部分可能须要改动一下,例如html中引入不支持ES6语法。

Vue组件(不存在问题3)

<template>
  <span></span>
</template>

<script> import { renderByMathjax } from '../utils' export default { name: 'MathJax', props: { latex: { // 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
npm i --save mathjax-vue

// yarn
yarn add mathjax-vue

// vue3中把mathjax-vue改为mathjax-vue3
复制代码

全局注册

import MathJax, { initMathJax, renderByMathjax } from 'mathjax-vue'

function onMathJaxReady() {
  const el = document.getElementById('elementId')
  renderByMathjax(el)
}

initMathJax({}, onMathJaxReady)

// vue 2
Vue.use(MathJax)

// vue3
createApp(App).use(MathJax)
复制代码

私有组件

import { MathJax } from 'mathjax-vue'
// 必须先执行过initMathJax
export default {
  ...
  components: {
    MathJax,
  },
  ...
}
复制代码

不想插入组件

// 必须先执行过initMathJax
import { renderByMathjax } from 'mathjax-vue'

renderByMathjax(document.getElementById('id1'))
复制代码

最后说一段题外话,笔者最近在准备开源一个数学公式编辑器,主要也是目前开源的数学公式编辑器没法知足业务需求,有须要的能够关注一下。若是本文有帮助到你,动动你的小手点个赞呗~

在线Demo:CodeSandbox 项目仓库:github