最近,一個朋友要求做一個數學編輯器,方便數學公式的錄入,特別是微積分、矩陣等公式,普通錄入非常麻煩,這里,花了一周時間,做了一個數學公式在線編輯功能。
下面記錄一下打造的過程。但是,目前很遺憾,這個系統還不支持導入導出功能。
如何實現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飛來飛去的了。
?
<div>
<div>
<textarea id="txt_question"></textarea>
</div>
<div>
<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) {
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);
}
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);
}
});
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);
}
};
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;
});
editor.on('SetContent', function(e) {
if (editor.getDoc().defaultView.MathJax) {
editor.getDoc().defaultView.MathJax.startup.getComponents();
editor.getDoc().defaultView.MathJax.typeset();
}
});
editor.ui.registry.addButton('插入公式', {
text: '插入公式',
tooltip: '插入公式',
onAction: function () {
openMathjaxEditor();
}
});
editor.on("click", function (e) {
let closest = e.target.closest('.' + mathjaxClassName);
if (closest) {
openMathjaxEditor(closest);
}
});
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);
}
}
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 target="blank" >LaTex說明</a> <a target="blank" >啟明星官網</a> <style>.tox-textarea{height:150px !important; border-radius:0px;}</style> </div>'
},
{
type: 'textarea',
name: 'title'
},
{
type: 'htmlpanel',
html: '<iframe id="' + mathjaxId + '" ></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;
}
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];
let getMathText = function (value, symbols) {
if (!symbols) {
symbols = mathjaxSymbols;
}
return symbols.start + ' ' + value + ' ' + symbols.end ;
};
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);
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_孟】!
總結
以上是生活随笔為你收集整理的十分钟打造一款在线的数学公式编辑器的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。