十分鍾打造一款在線的數學公式編輯器


最近,一個朋友要求做一個數學編輯器,方便數學公式的錄入,特別是微積分、矩陣等公式,普通錄入非常麻煩,這里,花了一周時間,做了一個數學公式在線編輯功能。

下面記錄一下打造的過程。但是,目前很遺憾,這個系統還不支持導入導出功能。

如何實現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

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

備份下載:MathEditor5.0.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM