ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

十分钟打造一款在线的数学公式编辑器

2021-03-10 10:02:05  阅读:290  来源: 互联网

标签:function 数学公式 latex 编辑器 十分钟 let editor div math


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

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

如何实现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="width:50%">
<textarea id="txt_question"></textarea>
</div>


<div style="width: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(/\n/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可以获取值。

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

?
1 var question = editor.getValue().replace(/\n/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%;   width: 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;     width: 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="width: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

标签:function,数学公式,latex,编辑器,十分钟,let,editor,div,math
来源: https://www.cnblogs.com/mq0036/p/14509808.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有