Skip to content

VUE使用MathJax展示LaTeX数学公式和实现ChatGPT打字机效果

🕒 Published at:

使用MathJax展示LaTeX数学公式

引入MathJax库

由于MathJax库体积较大,我们采用CDN的方式引入:

html
<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>

MathJax配置文件

javascript
// MathJax.js

let isMathjaxConfig = false; // 用于标识是否配置

const initMathjaxConfig = () => {
  if (!window.MathJax) {
    return;
  }
  window.MathJax.Hub.Config({
    showProcessingMessages: false, // 关闭js加载过程信息
    messageStyle: 'none', // 不显示信息
    jax: ['input/TeX', 'output/HTML-CSS'],
    tex2jax: {
      inlineMath: [['$', '$'], ['\\(', '\\)']], // 行内公式选择符
      displayMath: [['$$', '$$'], ['\\[', '\\]']], // 段内公式选择符
      skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'a'] // 避开某些标签
    },
    'HTML-CSS': {
      availableFonts: ['STIX', 'TeX'], // 可选字体
      showMathMenu: false // 关闭右击菜单显示
    }
  });
  isMathjaxConfig = true; // 配置完成,改为true
};

const MathQueue = function (elementId) {
  if (!window.MathJax) {
    return;
  }
  window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, document.getElementsByClassName(elementId)]); // 根据class
  // window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, document.getElementById(elementId)]); // 根据id
};

export default {
  isMathjaxConfig,
  initMathjaxConfig,
  MathQueue
};

在VUE中局部使用

javascript
import MathJax from '../util/MathJax';

this.$nextTick(function () { // Vue的DOM渲染是异步的
  if (!MathJax.isMathjaxConfig) { // 是否配置MathJax
    console.log('是否配置MathJax');
    MathJax.initMathjaxConfig();
  }
  MathJax.MathQueue('latexId'); // 渲染对应的id/class
});

实现ChatGPT打字机效果

打字机队列实现

javascript
// Typer.js
class Typewriter {
  constructor(onConsume) {
    this.queue = [];
    this.consuming = false;
    this.timmer = null;
    this.onConsume = onConsume;
  }

  // 输出速度动态控制
  dynamicSpeed() {
    const speed = 2000 / this.queue.length;
    return speed > 200 ? 200 : speed;
  }

  // 添加字符串到队列
  add(str) {
    if (!str) return;
    this.queue.push(...str.split(''));
  }

  // 消费
  consume() {
    if (this.queue.length > 0) {
      const str = this.queue.shift();
      str && this.onConsume(str);
    }
  }

  // 消费下一个
  next() {
    this.consume();
    this.timmer = setTimeout(() => {
      this.consume();
      if (this.consuming) {
        this.next();
      }
    }, this.dynamicSpeed());
  }

  // 开始消费队列
  start() {
    this.consuming = true;
    this.next();
  }

  // 结束消费队列
  done() {
    this.consuming = false;
    clearTimeout(this.timmer);
    this.onConsume(this.queue.join(''), true);
    this.queue = [];
  }
}

export default Typewriter;

在VUE中使用打字机效果

javascript
import Typewriter from '../util/Typer';

const typewriter = new Typewriter(this.writing);

// 开始打字
typewriter.start();

// 数据填充
typewriter.add(msg.data);

// 结束打字
typewriter.done();

打字机样式

css
.typering::after {
  content: '_';
  animation: blink 1s infinite;
}

@keyframes blink {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

输出字符处理

javascript
writing(chatData, done = false) {
  const dom = document.querySelector('.chat-show');
  dom.innerHTML += chatData;
  if (done) {
    dom.classList.remove('typering');
    this.$nextTick(function () { // Vue的DOM渲染是异步的
      if (!MathJax.isMathjaxConfig) { // 是否配置MathJax
        console.log('是否配置MathJax');
        MathJax.initMathjaxConfig();
      }
      MathJax.MathQueue('latexId'); // 渲染对应的id/class
    });
  }
}

参考链接