使用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

相關文章
相關標籤/搜索