zoukankan      html  css  js  c++  java
  • 十分钟打造一款在线的数学公式编辑器

    最近,一个朋友要求做一个数学编辑器,方便数学公式的录入,特别是微积分、矩阵等公式,普通录入非常麻烦,这里,花了一周时间,做了一个数学公式在线编辑功能。

    下面记录一下打造的过程。但是,目前很遗憾,这个系统还不支持导入导出功能。

    如何实现web录入的试题导出到word或者把word试题导入到系统,如果您有好的方法,欢迎推荐。(感觉要自己写解析Latex)

    在线体验  http://demo.dotnetcms.org/math  免费下载  https://files.cnblogs.com/files/mqingqing123/math5.0.rar

    1.MathJax

    在数学公式里,最流行的是 http://www.mathjax.org ,Mathjax支持数理化等各种公式,其实如果你希望只针对数学录入,可以使用 https://katex.org/ KaTex更简单、速度更快。

    Mathjax的文档里列出了MathJax目前支持的LaTex语法。对于未实现的语法,可以自定义宏来实现。

    从声明里看到实现了 sin,cos,tan,ctan等都支持,但是一些反正切没实现。

    所以,在MathJax的全局配置里,定义一个macros

    复制代码
        <script>
            MathJax = {
                options: {
                    enableMenu: false,
                    a11y: {
                    speech: false,                      // switch on speech output
                    braille: false,                     // switch on Braille output
                    subtitles: false
                   }
            },
    
                tex: {
                    inlineMath: [['@', '@'], ['\(', '\)']],
                    displayMath: [['@@', '@@'], ['\[', '\]']],
                    macros: {
                        arcsec: '\DeclareMathOperator{\arcsec}{arcsec}\arcsec',
                        arccsc: '\DeclareMathOperator{\arccsc}{arccsc}\arccsc',
                        arccot: '\DeclareMathOperator{\arccot}{arccot}\arccot'
                    }
                }
            }
    </script>
    复制代码

    然后引入Mathjax库

    1
    <script src="../js/math/tex-chtml-full.js"></script>

      

    另外,对于数学公式的“开始”和“结束”,MathJax默认使用""""和" "作为分割的,

    如果是块状的则使用"\["和"\]"区分,

    参考下图,左边是录入的内容,右边是显示的结果。

    但是Mathjax允许你自定义公式识别符,

    上面代码,我增加了“@”作为行内公式,使用"@@"作为块公式。

    其实,在选型时,作者测试了“$”或者“#”作为分隔符,但是最终确定使用@符号,最根本的原因是:

    在录入时,只有@符号,在中英模式下是一样的。

    现在老师可以像写文本一样,写题目了。

    2.引入CodeMirror

    在录入页面,引入Codemirror美化录入界面。

    毕竟,textarea默认太丑了。

    1
    2
    <link href="../js/codeMirror/lib/codemirror.css" rel="stylesheet" />
    <script src="../js/codeMirror/lib/codemirror.js"></script>

      

    初始化文本框,整个布局分左右布局,

    左边是文本框textarea进入录入,右边是iframe进行预览,

    在父div里,设置display为flex,进行左右布局,这样就不用 float 飞来飞去的了。

    1
     

    <div style="display:flex">
    <div style="50%">
    <textarea id="txt_question"></textarea>
    </div>


    <div style="50%; background-color:#f2f2f2">

    <iframe id=preview frameborder="0"
    width="100%"
    scrolling="no" >
    </iframe>
    </div>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <br>    <br><script>
     
            var delay;
            var editor = CodeMirror.fromTextArea(document.getElementById('txt_question'), {
                lineNumbers: true,
                mode: 'text/html',
                lineWrapping:true
            });
     
     
            editor.on("change", function () {
                clearTimeout(delay);
                delay = setTimeout(updatePreview, 500);
            });
     
     
     
            function updatePreview() {
                var iframe = document.getElementById('preview');
                var doc2 = iframe.contentDocument || iframe.contentWindow.document;
                let body2 = doc2.getElementsByTagName('body')[0];
                var data = editor.getValue().replace(/ /g, "<br>");
                body2.innerHTML = "<div class=mathjax-qmx>" + data + "</div> ";
                if(doc2.defaultView.MathJax!=null)
                {
                    doc2.defaultView.MathJax.typeset();
                }
            }
     
            setTimeout(updatePreview, 500);
     
        </script>

      

    在预览时,需要通过JS引入Mathjax

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <script>
     
          $(document).ready(function () {
              let iframe = document.getElementById("preview");
              let iframeWindow = iframe.contentWindow || iframe.contentDocument.document || iframe.contentDocument;
              let doc3 = iframeWindow.document;
     
              let head3 = doc3.getElementsByTagName('head')[0];
              let body3 = doc3.getElementsByTagName('body')[0]; 
     
          
              let js1 = doc3.createElement('script');
              js1.src = "../js/math/math-config.js";
              js1.type = 'text/javascript'
              head3.appendChild(js1);
              
     
              let js2 = doc3.createElement('script');
              js2.src = "../js/math/tex-mml-chtml.js";
              js2.type = 'text/javascript';
              js2.async = true;
              js2.charset = 'utf-8';
              head3.appendChild(js2);
          });
     
     
      </script>

      

    最后使用codemirror提供的getValue可以获取值。

    另外,在预览时,会把回车“ ”替换为“<br>”

    1
    var question = editor.getValue().replace(/ /g, "<br>")+"";

      

    这样就可以获取录入的值。

    3.打造菜单

    为了方便录入,打造了一个菜单,

    菜单布局父class是math-menu,子菜单由sub-math-menu包裹。下面是HTML代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
           <div class="math-menu"  data-editorid="editor">
     
               
              <a href="###">菜单1</a>
               <div class="sub-math-menu">
                   <span class="subnavbtn9">希腊字母  <span class="drop"></span> </span>
                   <div class="subnav-content9">
                       <div>小写字母</div>
                       <a class="add" data-math="alpha">@alpha@</a>
    <div style="clear:both"></div>
     
                 </div>
     </div>
     </div>

      

    下图是预览效果。

    下面是CSS样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    .math-menu {
      overflow: hidden;
      background-color: #f2f2f2;
    }
      
     
    .math-menu a {
      float: left;
      font-size: 16px;
      color: #000;
      text-align: center;
      padding: 14px 16px;
      text-decoration: none;
    }
     
    .math-menu .sub-math-menu a {
      
      font-size: 14px;
      padding: 12px 14px;
      
    }
     
    .sub-math-menu {
      float: left;
      overflow: hidden;
    }
     
     
    .sub-math-menu .subnavbtn9 {
      font-size: 16px; 
      border: none;
      outline: none;
      color: #000;
      padding: 14px 16px;
      background-color: inherit;
      font-family: inherit;
      margin: 0;
      display:flex;
    }
     
     
    .math-menu a:hover, .sub-math-menu:hover .subnavbtn9 {
      background-color: #ccc;
    }
     
     
     
    .subnav-content9 {
      display: none;
      position:absolute;
      background-color: #ccc;
      z-index: 1000;
      left:12.5%;
       75%;
    }
     
     
     
    .subnav-content9 a {
      float: left;
      color: #000;
      text-decoration: none;
       height:50px;
    }
     
    .subnav-content9 a:hover {
      background-color: #ffffff;
      color: black;
    }
     
      
     
     .drop{
            margin-top:10px;
            margin-left:2px;
         0;
        height: 0;
        border-left: 6px solid transparent;
        border-right: 6px solid transparent;
        border-top: 7px solid #333;
    }
     
       .CodeMirror {
      border: 1px solid #eee;
      height: 400px;
        
      word-break:break-all;
       font-family:Verdana;
    }
        .add{ cursor:pointer; }
              .layui-card{ margin-bottom:15px; }

      

    增加鼠标经过,菜单显示效果。

    注意:这里使用的是mouseover事件,而不是mouseenter事件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
                  <script>
     
                      $('.sub-math-menu').mouseover(function () {
          
                          $(this).find(".subnav-content9").show();
     
                      })
     
                      $('.sub-math-menu').mouseout(function () {
                          $(this).find(".subnav-content9").hide();
                      })
     
                      $(".add").click(
                          function ()
                          {
                              var ed=  $(this).parent().parent().parent().data("editorid");
                                
                              if(ed=="editor")
                              {
                                  editor.replaceSelection("@"+$(this).data("math")+"@")
                              }
                              else
                              {
                                  editor2.replaceSelection("@"+$(this).data("math")+"@")
                              }
     
                              $(this).parent().parent().find(".subnav-content9").hide();
     
                          }
     
                          );
    </script>

      

    到此,大功告成。

    4.打造普通模式(小白模式)

     当然,有时候你可能希望更多的控制,例如插入表格)

    这里使用Tinymce集成Mathjax实现,其中,这里使用一个插件:https://github.com/dimakorotkov/tinymce-mathjax

    代码里,扩展了Tinymce菜单的定制。

    默认这个插件提供的弹窗太小,可以放大,修改后代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    tinymce.PluginManager.add('mathjax', function(editor, url) {
     
      // plugin configuration options
      let mathjaxClassName = editor.settings.mathjax.className || "math-tex";
      let mathjaxTempClassName = mathjaxClassName + '-original';
     
     
      mathjaxSymbols = editor.settings.mathjax.symbols || { start: '\(', end: '\) ' };
     
     
      let mathjaxUrl = editor.settings.mathjax.lib || null;
      let mathjaxConfigUrl = (editor.settings.mathjax.configUrl || url + '/config.js') + '?class=' + mathjaxTempClassName;
      let mathjaxScripts = [mathjaxConfigUrl];
      if (mathjaxUrl) {
        mathjaxScripts.push(mathjaxUrl);
      }
     
      // load mathjax and its config on editor init
      editor.on('init', function () {
        for (let i = 0; i < mathjaxScripts.length; i++) {
          let id = editor.dom.uniqueId();
          let script = editor.dom.create('script', {id: id, type: 'text/javascript', src: mathjaxScripts[i]});
          editor.getDoc().getElementsByTagName('head')[0].appendChild(script);
        }
      });
     
      // remove extra tags on get content
      editor.on('GetContent', function (e) {
        let div = editor.dom.create('div');
        div.innerHTML = e.content;
        let elements = div.querySelectorAll('.' + mathjaxClassName);
        for (let i = 0; i < elements.length; i++) {
          let children = elements[i].querySelectorAll('span');
          for (let j = 0; j < children.length; j++) {
            children[j].remove();
          }
          let latex = elements[i].getAttribute('data-latex');
          elements[i].removeAttribute('contenteditable');
          elements[i].removeAttribute('style');
          elements[i].removeAttribute('data-latex');
          elements[i].innerHTML = latex;
        }
        e.content = div.innerHTML;
      });
     
      let checkElement = function(element) {
        if (element.childNodes.length != 2) {
          element.setAttribute('contenteditable', false);
          element.style.cursor = 'pointer';
          let latex = element.getAttribute('data-latex') || element.innerHTML;
          element.setAttribute('data-latex', latex);
          element.innerHTML = '';
     
          let math = editor.dom.create('span');
          math.innerHTML = latex;
          math.classList.add(mathjaxTempClassName);
          element.appendChild(math);
     
          let dummy = editor.dom.create('span');
          dummy.classList.add('dummy');
          dummy.innerHTML = 'dummy';
          dummy.setAttribute('hidden', 'hidden');
          element.appendChild(dummy);
        }
      };
     
      // add dummy tag on set content
      editor.on('BeforeSetContent', function (e) {
        let div = editor.dom.create('div');
        div.innerHTML = e.content;
        let elements = div.querySelectorAll('.' + mathjaxClassName);
        for (let i = 0 ; i < elements.length; i++) {
          checkElement(elements[i]);
        }
        e.content = div.innerHTML;
            
      });
     
      // refresh mathjax on set content
      editor.on('SetContent', function(e) {
        if (editor.getDoc().defaultView.MathJax) {
          editor.getDoc().defaultView.MathJax.startup.getComponents();
          editor.getDoc().defaultView.MathJax.typeset();
        }
      });
     
      // add button to tinimce
      editor.ui.registry.addButton('插入公式', {
        text: '插入公式',
        tooltip: '插入公式',
        onAction: function () {
            openMathjaxEditor();
     
            
        }
      });
     
      // handle click on existing
      editor.on("click", function (e) {
        let closest = e.target.closest('.' + mathjaxClassName);
        if (closest) {
          openMathjaxEditor(closest);
        }
      });
     
     
     
     
     
     
      // open window with editor
      let openMathjaxEditor = function(target) {
          
        let mathjaxId = editor.dom.uniqueId();
         
        let latex = '';
        if (target) {
          latex_attribute = target.getAttribute('data-latex');
          if (latex_attribute.length >= (mathjaxSymbols.start + mathjaxSymbols.end).length) {
            latex = latex_attribute.substr(mathjaxSymbols.start.length, latex_attribute.length - (mathjaxSymbols.start + mathjaxSymbols.end).length);
          }
        }
       
     
        // show new window
        editor.windowManager.open({
            title: 'Mathjax',
            size: 'medium',
            body: {
             type: 'panel',
             items: [
                 {
                     type: 'htmlpanel',
                     html: '<div > <input onclick=changesybol() type=checkbox id=cb_br name=cb_br>换行 <a href="https://www.cnblogs.com/mqingqing123/p/12063096.html" target="blank" >LaTex说明</a>   <a href="http://www.dotnetcms.org" target="blank" >启明星官网</a> <style>.tox-textarea{height:150px !important;  border-radius:0px;}</style> </div>'
                 },
                {
                type: 'textarea',
                name: 'title'
                },
                 {
                    type: 'htmlpanel',
                    html: '<iframe id="' + mathjaxId + '" style="98%; min-height: 50px;    "  ></iframe>'
                }
             ]
          },
     
          buttons: [{ type: 'submit', text: '确定' }],
     
          onSubmit: function onsubmit(api) {
            let value = api.getData().title.trim();
            if (target) {
              target.innerHTML = '';
              target.setAttribute('data-latex', getMathText(value));
              checkElement(target);
            } else {
              let newElement = editor.getDoc().createElement('span');
              newElement.innerHTML = getMathText(value);
              newElement.classList.add(mathjaxClassName);
              checkElement(newElement);
              editor.insertContent(newElement.outerHTML);
            }
            editor.getDoc().defaultView.MathJax.startup.getComponents();
            editor.getDoc().defaultView.MathJax.typeset();
            api.close();
          },
          onChange: function(api) {
            var value = api.getData().title.trim();
            if (value != latex) {
              refreshDialogMathjax(value, document.getElementById(mathjaxId));
              latex = value;
            }
          },
          initialData: {title: latex}
        });
      
        if (mathjaxSymbols.start == "\(") {
            document.getElementById("cb_br").checked = false;
        }
        else {
            document.getElementById("cb_br").checked = true;
        }
       
     
        
     
        // add scripts to iframe
        let iframe = document.getElementById(mathjaxId);
     
        let iframeWindow = iframe.contentWindow || iframe.contentDocument.document || iframe.contentDocument;
        let iframeDocument = iframeWindow.document;
        let iframeHead = iframeDocument.getElementsByTagName('head')[0];
        let iframeBody = iframeDocument.getElementsByTagName('body')[0];
       
        // get latex for mathjax from simple text
        let getMathText = function (value, symbols) {
          if (!symbols) {
            symbols = mathjaxSymbols;
          }
          
          return symbols.start + ' ' + value + ' ' + symbols.end ;
        };
     
        // refresh latex in mathjax iframe
        let refreshDialogMathjax = function(latex) {
          let MathJax = iframeWindow.MathJax;
          let div = iframeBody.querySelector('div');
          if (!div) {
            div = iframeDocument.createElement('div');
            div.classList.add(mathjaxTempClassName);
            iframeBody.appendChild(div);
          }
          div.innerHTML = getMathText(latex, {start: '$$', end: '$$'});
          if (MathJax && MathJax.startup) {
            MathJax.startup.getComponents();
            MathJax.typeset();
          }
        };
        refreshDialogMathjax(latex);
     
        // add scripts for dialog iframe
        for (let i = 0; i < mathjaxScripts.length; i++) {
          let node = iframeWindow.document.createElement('script');
          node.src = mathjaxScripts[i];
          node.type = 'text/javascript';
          node.async = false;
          node.charset = 'utf-8';
          iframeHead.appendChild(node);
        }
     
      };
    });
     
     
     
    function changesybol() {
        if (document.getElementById("cb_br").checked) {
            mathjaxSymbols = { start: '\[', end: '\] ' };
        }
        else {
            mathjaxSymbols = { start: '\(', end: '\) ' };
        }
     
     
    }

      

    这样,这个系统核心就完成了。

    在线体验  http://demo.dotnetcms.org/math

    出处:https://www.cnblogs.com/mqingqing123/p/14509366.html

    =======================================================================================

    备份下载:MathEditor5.0.rar

    您的资助是我最大的动力!
    金额随意,欢迎来赏!
    款后有任何问题请给我留言。

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我。(●'◡'●)

    如果你觉得本篇文章对你有所帮助,请给予我更多的鼓励,求打             付款后有任何问题请给我留言!!!

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【Jack_孟】!

  • 相关阅读:
    列表,表格与媒体元素
    【Mac + Appium + Python3.6学习(三)】之IOS自动化测试环境配置
    Mac 下的自动化学习
    【Mac + Appium + Python3.6学习(二)】之Android自动化测试,appium-desktop配置和简易自动化测试脚本
    【Mac + Appium学习(一)】之安装Appium环境前提准备
    anyproxy-初识使用
    fiddler 学习教程
    Linux学习
    python 接口自动化
    python 学习教程
  • 原文地址:https://www.cnblogs.com/mq0036/p/14509808.html
Copyright © 2011-2022 走看看