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作者的出发点是为了让每个组件都可以支持不同配置,可关键点是作者代码没处理好,埋藏了性能问题。

    开始造轮子

    // 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