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

4,073 阅读2分钟

在这里插入图片描述

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

引言

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

MathJax 是什么

在 MathJax 官网 能看到以下的介绍:

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

No more setup for readers. It just works.

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

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

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)....

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.

要点总结如下:

  • 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