AngularMathJax

我是如何解决 Angular 上用 MathJax 的一些问题的

话说我本来是倾向于 KaTeX 的,因为我感觉他很快,而且 MathJax 似乎很难配。但是大家表示对缺少功能的 KaTeX 并无好感,给我提供了一些钻研 MathJax 的动力。

其实也不算钻研,因为实际上 MathJax 很简单,调用 MathJax.Hub.Queue(['Typeset', MathJax.Hub, this.element.nativeElement]); 就可以渲染一个元素(这个 this.element.nativeElement 是从 Angular 中调用它 DOM 的语法),这个 .Queue 实际上是 MathJax 自己实现的回调格式,语法非常清奇,参数个数不定,每个都是数组,代表一个回调,顺序执行。比如这个 ['Typeset', MathJax.Hub, this.element.nativeElement],第一个元素是方法名,第二个元素是 this,之后的元素都是参数……

我们可以看到这个就相当于执行 MathJax.Hub.Typeset(this.element.nativeElement),那为啥不执行这个?因为这方法是同步的,会导致页面十分卡。于是 MathJax 就自己封装了一个异步队列(它的 API 可能几百年没改了)

我们说回 Angular。因为要用 markdown,我的思路是用 marked 封装一个 directive。那么我们就应该在 marked 渲染完成之后用 MathJax 去 Typeset 这个组件。但真的这样做了,却产生了奇妙的效果——切换页面之后,要等将近一分钟才开始渲染。我在它的队列里放了几个 log,发现每个元素都被 queue 了 4 次,几十个元素,难怪要一分钟才开始渲染下一页的内容,即使大部分 markdown 里面根本没有数学。

这时候我开始灰心了,这个问题就没有解决办法了吗?绝望之时,我想到能不能直接 Typeset document,结果是可以的,而且十分快。所以渲染并不慢,可能是渲染的初始化过程比较慢。那么这时候方案就出来了,我们可以尽量减少渲染次数,同时只渲染 document。只要这个渲染还在进行,那么有再多的元素 queue 上来,我们也只当作 queue 了一次。

于是我就写了这么个 service:

@Injectable()
export class MathjaxService {

  public isQueued = false;
  public isRunning = false;
  window: any;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    if (isPlatformBrowser(this.platformId)) {
      this.window = window as any;
    }
  }

  finishRunning() {
    this.isRunning = false;
    if (this.isQueued) {
      this.queueChange();
    }
  }

  queueChange() {
    if (this.isRunning) {
      this.isQueued = true;
    } else {
      this.isQueued = false;
      this.isRunning = true;
      if (isPlatformBrowser(this.platformId)) {
        if (this.window.MathJax) {
          this.window.MathJax.Hub.Config({
            messageStyle: 'none',
            tex2jax: {
              // preview: 'none',
              inlineMath: [['`_mathjax_internal$', '$]^$`']],
              processEscapes: true
            }
          });
          this.window.MathJax.Hub.Queue(['log', console, 'start'], ['Typeset', this.window.MathJax.Hub, document], ['log', console, 'end'], ['finishRunning', this]);
        }
      } else {
        this.finishRunning();
      }
    }
  }
}

事实证明,它能圆满完成任务,它也就是现在运行在这个网站上的代码。

Colliot12/14/2017, 5:20:31 AM

好顶赞,可能需要给帖子下面加个留白,我这写回复的时候太靠下了,看着不舒服

zuozijian372012/14/2017, 5:23:18 AM

你说的非常对,移动版上问题更大……

Colliot12/14/2017, 5:28:34 AM

我突然感觉不停渲染数学,挺吃 CPU 的……

Colliot12/14/2017, 5:58:16 AM

事实证明,结合 markdown 和 MathJax 远没有这么简单。遇到的问题有:

  1. \ 被转义的问题(我打的是一个,但为了暂时规避这个问题,我把单个反斜杠都替换成了双的。
  2. 摘要的时候如何过滤掉 _mathjax_internal$` 的问题。现在是一刀切的,但是对于代码里的,并不需要一刀切。
  3. 无限的 $]^$` 导致的空内容绕过检查的问题。用数学模式可以发出实际是空的,但有很多字的内容。

这需要我们对 markdown 的 parser、甚至是 MathJax 的 parser 有进一步的 hack,现有的似乎都不能满足要求。MSE 倒是看起来不错,不知道他们做了哪些改进。

Colliot12/18/2017, 8:46:11 PM

为什么Jekyll就不存在这些问题?虎哥你懂吗

ice100012/18/2017, 9:40:04 PM

或许你需要在渲染md的时候把MathJax块单独提取出来。渲染完了再放回去。

ice100012/18/2017, 9:40:39 PM

是的,所以我们需要写一个结合了数学和 markdown 的 parser。

Colliot12/18/2017, 9:41:25 PM

怎么引入啊

Sum1Dream5/14/2020, 11:07:22 PM

Preview:

Cancel

Elsewhere

hereIsMyNickname replied to Rust 相比 C++ 有什么决定性的好处吗?

Rust能给世界带来love,you never have loved enough

Colliot replied to 新版的 WebStorm/JetBrains 系 IDE 的 zsh 历史,跟单独打开 terminal 的历史不同步

根据 zsh history not preserved in Terminal,似乎是一项叫 Shell integration 的设置导致了 HISTFILE 这个环境变量被改成了 IDE 自己的路径(在这个 .app 包里),而这个环境变量正是指定了 zsh 读取历史记录的地方。 里面说了一个绕过的方法是,手动在 .zshrc 里指定 HISTFILE 这个环境变量为默认的 ~/.zsh_history。

Colliot replied to 如何接入 GitHub 登陆认证?

需要我自己的服务端做什么吗?

Colliot replied to 网站搜索的最佳解决方案是什么?

Elasticsearch 的主要问题是不是太吃内存了,不适合单机部署,只能组内网?

hardfist replied to Git 如何 pull --force?

git fetch --all && git reset --hard origin/xxx

hardfist replied to lerna 和 yarn workspace 有什么关系?

yarn的workspace好像是脱胎于lerna,后来lerna又反过来支持了yarn workspace

Colliot replied to 我终于找到 CI fail 的原因了!

可以使用我修改过的这个分支 Fixes offline mirror filename calculation for scoped packages URLs in the taobao npm registry #7533

someone replied to 我终于找到 CI fail 的原因了!

所以,如何避免这个问题?只能替换淘宝源为官方源吗?

nickname replied to Git 如何 pull --force?

-f force When git fetch is used with <src>:<dst> refspec it may refuse to update the local branch as discussed in the <refspec> part of the git-fetch[1] documentation. This option overrides that check. https://git-scm.com/docs/git-pull#Documentation/git-pull.txt---force