标签: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. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。