Vue 中 MathJax 的使用与渲染的监听 (上)

在这里插入图片描述

本文做者:傅云贵(网易有道技术团队)javascript

引言

最近,在桌面 web 前端项目中使用了 MathJax 渲染数学公式,遇到一些坑,现在总结之。html

MathJax 是什么

在 MathJax 官网 能看到如下的介绍:前端

A JavaScript display engine for mathematics that works in all browsers.java

No more setup for readers. It just works.es6

在 MathJax 的官方文档 What is MathJax? 中有如下文字:web

MathJax is an open-source JavaScript display engine for LaTeX and MathML that works in all modern browsers. ...ajax

MathJax uses web-based fonts (in those browsers that support it) to produce high-quality typesetting that scales and prints at full resolution (unlike mathematics included as images)....npm

MathJax is modular, so it loads components only when necessary, and can be extended to include new capabilities as needed. MathJax is highly configurable, allowing authors to customize it for the special requirements of their web sites. Finally, MathJax has a rich application programming interface (API) that can be used to make the mathematics on your web pages interactive and dynamic.浏览器

要点总结以下:markdown

  • MathJax 是开源的高质量的数学公式渲染引擎
  • 支持现代浏览器
  • 模块化,按需加载
    • 高配置性
    • 接口丰富
    • 总文件很是大,只能按需加载

通常使用

正如 MathJax 官网 所说的 「 It just works.」同样, 通常使用 mathjax 很是简单:

If you write your own HTML (directly or via a template/theme engine), you can include MathJax by adding this snippet to your page:

<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
复制代码

且提供了一个示例:

Here's a pre-populated example on jsbin you can re-use.

即只要在 html 引入 MathJax 种子文件,MathJax 会在 ready 后,会自动去渲染document.body 中的数学公式——MathJax 将该过程称为Startup Typeset。

Vue 中使用

在 Vue 中,组件在mounted/updated生命周期后,才完成组件 html 的渲染。 故可在组件mounted/updated生命周期后调用 MathJax 对组件 html 进行数学公式渲染——MathJax 将这一过程叫作Typeset。

使用代码表示以下:

@Component({})
class SomeComponent extends Vue {
    private callMathJaxTypeset(): void {
        // call window.MathJax to typeset
        const { typesetElement } = this.$refs
        MathJax.Hub.Queue(['Typeset', MathJax.Hub, typesetElement])
    }

    mounted(): void {
        this.callMathJaxTypeset()
    }

    updated(): void {
        this.callMathJaxTypeset()
    }
}

复制代码

MathJax 加载

通常来讲,MathJax 种子文件不该当放在 html——即不一开始就加载,除非整个 webapp 都用到了 MathJax。

更为理想的情况是 MathJax 种子文件按需加载, 故实现一个加载 MathJax 的函数 loadMathJax():

const CDN_URL =
    'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?' +
    'config=TeX-MML-AM_CHTML' +
    '&delayStartupUntil=configured'

let isLoading = false
let isConfigured = false

function waitUntil(callback: () => void, failCallback: () => void): void {
    // TODO
}

function isLoaded(): boolean {
    if (window.MathJax) {
        if (!isConfigured) {
            isConfigured = true
            window.MathJax.Hub.Config({
                skipStartupTypeset: true,
                messageStyle: 'none',
                tex2jax: {
                    inlineMath: [
                        // for recommend
                        ['$', '$'],
                        // the default config
                        ['\\(', '\\)'],
                    ],
                },
            })

            window.MathJax.Hub.Configured()
        }
        if (window.MathJax.isReady) {
            return true
        }
    }

    return false
}

function loadScript(): void {
    if (isLoaded() || isLoading) {
        return
    }
    isLoading = true
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = CDN_URL
    document.getElementsByTagName('head')[0].appendChild(script)
}

async function loadMathJax(): Promise<typeof MathJax> {
    return new Promise((resolve, reject) => {
        if (window.MathJax) {
            resolve(window.MathJax)
            return
        }

        waitUntil(
            () => {
                resolve(window.MathJax)
            },
            () => {
                reject()
            },
        )
        loadScript()
    })
}

export { loadMathJax }
复制代码

在loadMathJax() 的实现中,有如下几点须要注意:

  1. 跳过Startup Typeset
  • MathJax cdn url 中必须加上 &delayStartupUntil=configured
  • 调用window.MathJax.Hub.Config 时, skipStartupTypeset 设置为true
  • 调用 window.MathJax.Hub.Configured()
  1. 使用 window.MathJax.isReady 判断 MathJax 是否可用

Vue 组件加载与使用 MathJax

为了在 Vue 组件中按需加载与使用 MathJax,可在组件的created 生命周期中加载 MathJax:

@Component({})
class SomeComponent extends Vue {
    private mathJax: typeof MathJax | null = null

    private needTypeset: boolean = false

    private callMathJaxTypeset(): void {
        const { mathJax } = this
        if (mathJax) {
            const { typesetElement } = this.$refs
            mathJax.Hub.Queue(['Typeset', MathJax.Hub, typesetElement])
        } else {
            this.needTypeset = true
        }
    }

    created(): void {
        const mathJax = await loadMathJax()
        this.mathJax = mathJax

        if (this.needTypeset) {
            this.callMathJaxTypeset()
        }
    }

    mounted(): void {
        this.callMathJaxTypeset()
    }

    updated(): void {
        this.callMathJaxTypeset()
    }
}
复制代码

此时,能够在 Vue 组件使用 MathJax 渲染数学公式,知足单页面应用中显示数学公式的应用场景。

特殊需求

最近的产品中有一个需求

待 MathJax 渲染(Typeset)数学公式后,用户使用浏览器的打印功能打印网页。

在此需求中,须要判断全部组件实例的 MathJax Typeset 是否完成。

如何监听全部组件实例中的 MathJax Typeset 是否完成呢?且听下回分解。

网易技术热爱者队伍持续招募队友中!网易有道,与你同道,由于热爱因此选择, 期待志同道合的你加入咱们,简历可发送至邮箱:bjfanyudan@corp.netease.com