简介这是一套基于JavaScript与HTML5的轻量级公式编辑器网页源码适合Web前端开发者、在线教育工具设计者及数学排版爱好者参考用于在网页中完成数学公式的输入、编辑和函数图像展示。压缩包内仅包含2个文件一个HTML负责页面结构与交互容器一个JS文件承担事件监听、公式解析与绘图逻辑整体尺寸仅9KB便于快速阅读与修改。实现上覆盖了LaTeX公式渲染、Canvas/SVG图形绘制、WYSIWYG式即时预览、跨浏览器兼容处理等核心知识点展现了如何以极简代码打通公式编辑的完整流程。代码虽精简仍示范了如何利用addEventListener处理键盘输入与按钮点击以及通过DOM操作即时更新显示结果为后续深入KaTeX、MathJax或D3.js等库留下了清晰对接方式。已有1311人浏览/学习下载后可作为轻量级模板快速理解JavaScript在DOM操作、函数计算和前端绘图中的实际用法也可在此基础上扩展更多符号面板或高级函数支持。1. javascript公式编辑器到底解决什么问题从一段LaTeX字符串说起做过在线作业系统或者课程平台的人大概率遇到过这种需求学生要提交一个分式、一个求和符号甚至一段矩阵而普通的文本框只能输入纯文本。有人说传图片可图片没法批改、没法搜索、没法二次编辑有人说用现成的富文本编辑器但富文本编辑器对公式的支持要么付费要么依赖老旧插件。这时候你就会发现需要一个真正跑在浏览器里的javascript公式编辑器——它不是一个文本框而是一套「输入公式结构 实时渲染 数据回传」的完整闭环。这类编辑器的核心思路是用户在界面上通过按钮或键盘输入LaTeX语法编辑器实时调用渲染引擎把语法转成可视化的数学公式同时把LaTeX源码作为数据存进数据库。整套方案的价值在于公式不再是截图它是结构化文本可以在任何支持LaTeX的场合复用。适合谁用简单说凡是页面里需要输入数学公式、化学方程式或者需要把公式存进数据库并回显的前端项目都属于这个编辑器的目标场景。我们就从渲染引擎选型、代码接入、数据闭环到踩坑排查完整过一遍。2. 渲染引擎才是核心MathJax 与 KaTeX 的选型对比和取舍逻辑很多人第一次搭公式编辑器先把精力花在工具栏上结果到最后发现卡住自己的不是按钮而是公式渲染那一步。一个javascript公式编辑器本质上由两部分拼起来编辑器本体负责接收输入、维护光标状态渲染引擎负责把LaTeX源码变成看得懂的公式图像。编辑器可以自己写渲染引擎几乎没必要自己造轮子老老实实选一个成熟的开源渲染库。2.1 为什么说编辑器可以轻、渲染器必须重编辑器本体在公式场景里反而简单它不需要像代码编辑器那样处理缩进和语法高亮核心能力就是维护一段字符串以及把用户点击的按钮符号插入到光标位置。你可以用textarea也可以用内容可编辑的div甚至接一个代码编辑器组件来做输入区。但渲染器不一样。LaTeX语法本身是几十年的老体系有分式、根式、矩阵、上下标、多行对齐、各类符号甚至化学式、物理单位。要让浏览器把这一整套语法画成像素级的公式背后要做分词、语法解析、布局计算、字体匹配。自己做这个等于重新发明轮子。所以我一般建议编辑器组件选轻的能自己写就自己写渲染引擎用开源现成的把精力留给业务逻辑。2.2 两个主力渲染引擎的对比目前浏览器端最常用的两个LaTeX渲染引擎一个是MathJax一个是KaTeX。它们的定位不太一样选型直接影响页面性能和兼容性。对比维度MathJaxKaTeX渲染速度较慢尤其是大文档全量渲染时快单条公式渲染几乎没有感知延迟语法兼容性几乎完整的TeX语法兼容老文档覆盖主流语法个别冷门命令需要配置宏体积较大按需加载还需要额外配置较小适合前端打包输出形式支持HTMLCSS、SVG、MathML等主要以HTMLCSS输出也支持MathML适用场景文档类站点、PDF导出、老项目实时交互编辑器对延迟敏感的工具只看数据可能觉得MathJax全面占优但公式编辑器恰恰相反——我用KaTeX更多。原因很简单公式编辑器是强交互场景用户每敲一个字符预览区就要重新渲染一次。MathJax的渲染延迟在这种场景下会让人感觉卡顿打起字来像踩着刹车。KaTeX的渲染是同步的毫秒级出结果体感上顺畅很多。2.3 选型的三条判断准则第一看你的公式复杂度。如果项目里只需要分式、上下标、根号这类初等数学公式KaTeX完全够用。如果涉及复杂的化学式、多行方程组或者要兼容用户粘贴的老TeX文档MathJax的容错性会更好。第二看页面的交互密度。单条公式编辑用KaTeX但如果你要批量渲染一篇文章里几十上百条公式MathJax的异步渲染反而能避免阻塞页面而且它自带页面级扫描。第三看团队的维护成本。KaTeX接入简单一个render函数就能干活出问题也好排查。MathJax配置项多启动时还有字体加载流程新手容易被它的配置绕晕。我的建议是优先KaTeX遇到覆盖不了的语法再用MathJax做兜底。提示选定渲染引擎后建议把渲染逻辑封装成一个独立模块后续想从KaTeX切到MathJax只改这个模块页面其他地方不用动。3. 用原生JavaScript搭最小公式编辑器实时预览的最小可运行代码理论说完了直接动手。这一章我们不用任何框架用原生JavaScript搭一个最小可用的公式编辑器左边是textarea输入LaTeX右边是实时渲染的预览区。这个结构麻雀虽小五脏俱全理解了它你就能把它接进任何前端项目。3.1 最小可运行页面textarea加KaTeX渲染先写一个完整的HTML文件复制到本地就能跑。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / title最小公式编辑器 Demo/title !-- KaTeX 样式是渲染公式的必要条件 -- link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/katexlatest/dist/katex.min.css / !-- 引入 KaTeX 渲染引擎核心 -- script defer srchttps://cdn.jsdelivr.net/npm/katexlatest/dist/katex.min.js/script /head body !-- 左侧输入区 -- textarea idformula-input placeholder输入 LaTeX 公式例如 \frac{a}{b}/textarea !-- 右侧预览区 -- div idformula-preview/div script const input document.getElementById(formula-input); const preview document.getElementById(formula-preview); function renderFormula() { const latex input.value.trim(); if (!latex) { // 空输入时不渲染避免出现空白错误框 preview.textContent 公式预览会显示在这里; return; } try { // katex.render 直接把 LaTeX 渲染进指定 DOM 节点 katex.render(latex, preview, { displayMode: true, // 块级公式居中展示 throwOnError: false, // 解析失败时不要抛异常中断 errorColor: #cc0000 // 出错时用红色提示 }); } catch (e) { preview.textContent 公式解析失败 e.message; } } // input 事件保证每次输入都触发重渲染 input.addEventListener(input, renderFormula); renderFormula(); /script /body /html这段代码的逻辑很直白监听textarea的input事件每次拿到最新的LaTeX字符串调用katex.render()塞进预览区。displayMode: true表示按块级公式渲染公式会独占一行并居中如果只需要行内公式改成false就行。参数说明里有两个值得留意的点。throwOnError: false是必须设置的否则用户输入一个半截语法KaTeX直接抛异常整个预览区都白屏体验很难看。设成false后KaTeX会把错误信息渲染成一个红色文本页面不会崩。errorColor是配套项默认是红色你可以改成项目主题色。3.2 给编辑器加工具栏点击插入符号只有textarea还不够顺手真实项目里用户记不住LaTeX语法需要工具栏辅助输入。常见做法是点工具栏按钮把对应符号的LaTeX代码插入到光标位置。这里用textarea实现核心是操作selectionStart和selectionEnd。// 在 textarea 光标位置插入 LaTeX 片段 function insertAtCursor(textarea, latexSnippet) { const start textarea.selectionStart; const end textarea.selectionEnd; const value textarea.value; // 用 slice 拼接保留被选中的部分作为公式内容 const newValue value.slice(0, start) latexSnippet value.slice(end); textarea.value newValue; // 光标定位到插入内容之后 textarea.selectionStart textarea.selectionEnd start latexSnippet.length; // 手动触发 input 事件让预览区同步更新 textarea.dispatchEvent(new Event(input)); textarea.focus(); }这个函数的技巧点在于先记住光标位置替换选中文本再手动派发input事件让上一步的监听器重新渲染。不用重新绑定任何逻辑工具栏和输入框就联动起来了。实际项目里工具栏按钮一般按数学类别分组成上下标、分式根式、求和积分、括号定界符、矩阵等。每个按钮配置一个插入片段结构大致是这样const toolbarConfig [ { label: 分式, latex: \\frac{}{} }, { label: 上标, latex: ^{} }, { label: 下标, latex: _{} }, { label: 根号, latex: \\sqrt{} }, { label: 求和, latex: \\sum_{}^{} }, { label: 积分, latex: \\int_{}^{} }, { label: 矩阵, latex: \\begin{matrix} \\\\ \\end{matrix} } ];这里有个血泪经验插入片段里不要只写孤零零的符号要写成带大括号的模板比如\frac{}{}用户插入后直接在大括号里填内容。如果只插入\frac用户还得自己补大括号对新手来说很难受。更进一步的做法是插入后选中第一个空的大括号让用户直接输入覆盖。3.3 把编辑器封装成可复用模块demo跑通后下一步是封装。常见做法是写一个工厂函数传入配置对象返回一个编辑器实例。我一般这样组织function createFormulaEditor(container, options) { const { onChange, initialValue } options; // 创建输入区和预览区结构 container.innerHTML div classformula-editor textarea classformula-input${initialValue}/textarea div classformula-preview/div /div ; const textarea container.querySelector(.formula-input); const preview container.querySelector(.formula-preview); function render() { const latex textarea.value.trim(); preview.innerHTML ; if (!latex) return; try { katex.render(latex, preview, { displayMode: true, throwOnError: false }); } catch (e) { preview.textContent e.message; } // 把最新值回调给外部 onChange(textarea.value); } textarea.addEventListener(input, render); return { getValue: () textarea.value, setValue: (latex) { textarea.value latex; render(); }, insert: (snippet) insertAtCursor(textarea, snippet), destroy: () container.innerHTML }; }模块化带来了三个直接好处页面里可以挂多个编辑器实例互不干扰外部可以随时用getValue取数据、用setValue回填数据销毁时清空DOM避免内存泄漏。到这里一个能用的公式编辑器已经成形接下来要解决的是数据怎么存、怎么展示的问题。4. 公式数据怎么存、怎么回显、怎么导出三种形态与必调参数公式编辑器产出的是什么表面上是图片本质上是LaTeX字符串。这一步想不通后面就会在存储和导出上反复翻车。我见过有人把渲染后的HTML直接存进数据库结果回显时满屏样式冲突也有人存图片结果公式改一个字就得重新截图。正确做法是数据库存LaTeX源码展示时现渲染。4.1 前端接口设计LaTeX源码的获取与回填在表单场景里编辑器一般作为一个字段存在。提交时调用getValue()拿到LaTeX随表单一起提交编辑时拿到数据库里的LaTeX调setValue()把公式回填进编辑器。这种设计天然支持搜索也方便迁移。// 提交时获取公式 function handleSubmit() { const formulaLatex editor.getValue(); // 把 LaTeX 字符串作为字段值提交给后端 submitData({ answer: formulaLatex }); } // 编辑回显时填充公式 function handleEdit(existingLatex) { editor.setValue(existingLatex); }参数层面有一个常见的坑textarea里显示的是原始LaTeX还是格式化后的LaTeX我建议回填时保持原始字符串不要做多余的空格清理。原因在于LaTeX语法里某些命令对空格敏感比如\frac和\frac {}虽然KaTeX都认识但用户自己写的排版习惯可能依赖这些细节。渲染引擎不管但回填后再编辑时用户会发现原来的公式格式变了体验很差。4.2 回显场景与renderToString列表页、详情页、打印页里不需要完整的编辑器只需要把公式展示出来。这时候别再挂编辑器组件直接用katex.renderToString()把LaTeX转成HTML字符串塞进模板里就行。// 把 LaTeX 字符串转成 HTML 字符串用于列表展示 function latexToHtml(latex) { if (!latex) return ; try { return katex.renderToString(latex, { displayMode: false, // 行内公式 throwOnError: false, output: htmlAndMathml // 同时输出 MathML便于复制和无障碍阅读 }); } catch (e) { return span stylecolor:red公式解析失败/span; } }这里有个需要理解的差异render()是直接操作DOM适合编辑器内部的实时预览renderToString()返回字符串适合拼接到列表页或文章正文里。如果你在Vue或React里用公式展示正确姿势是在组件里调用renderToString把结果通过v-html或dangerouslySetInnerHTML渲染出去。4.3 导出复制到OfficeMathML与图片兜底老师批改场景经常要把公式复制到Word或WPS里。KaTeX导出HTML后浏览器复制时一般能带格式但跨应用时经常丢样式。更稳的做法是把公式输出成MathML或者SVG。// 导出 MathML 结构Word 对 MathML 有较好支持 const mathmlString katex.renderToString(latex, { output: mathml, throwOnError: false }); // 导出 SVG用于图片格式兜底 const svgString katex.renderToString(latex, { output: svg, throwOnError: false });不过从实际经验看MathML在Word里的兼容性也不是完美无缺涉及复杂矩阵时仍可能变形。最省事的兜底方案是后端用LaTeX渲染服务把公式转成PNG图片前端提供「复制图片」按钮。这样虽然多了一次网络请求但兼容性无死角。4.4 KaTeX三个必调参数速查参数这东西用的时候查文档效率最高。这里列一个我在公式编辑器里每次必配的参数表省得来回翻文档。参数建议值作用displayMode编辑器预览用true行内展示用false控制公式是块级居中还是行内嵌入throwOnErrorfalse语法出错时输出错误信息而不是抛异常白屏output默认html导出时用mathml或svg控制渲染输出的格式影响复制兼容性errorColor项目主题色或#cc0000错误信息的提示颜色strictfalse忽略一些冷门语法警告避免干扰正常渲染另外提醒一个性能参数如果页面里要同时渲染大量公式比如考试分析报告里一百道题带答案解析建议用katex.render()大量调用时先document.createDocumentFragment()收集节点最后一次性挂载避免每次渲染都触发浏览器重排。5. 公式编辑器常见坑位排查五条让我翻过车的细节记录这一章不讲原理只记录实际开发中遇到的翻车现场。每条都是「现象 → 原因 → 解决」等你真遇到类似问题时可以直接对照着查。5.1 反斜杠在JavaScript字符串里神秘消失现象在代码里写const latex \frac{a}{b}渲染出来只有frac{a}{b}前面的反斜杠没了。原因JavaScript字符串里\f是换页符转义\t是制表符这些反斜杠在字符串解析阶段就被吞掉了。LaTeX语法密集使用反斜杠直接和JS转义规则撞车。解决用String.raw模板字符串或者把反斜杠写成双反斜杠。// 使用 String.raw反斜杠被原样保留 const latex String.raw\frac{a}{b}; // 或者写成双反斜杠 const latex2 \\frac{a}{b};这个坑在写配置文件、写测试用例、写后端返回示例时最容易踩。如果你发现公式在页面上和数据库里不一致优先检查是不是字符串转义在中间环节作祟。5.2 KaTeX渲染报错导致整个页面白屏现象用户输入一个不完整的语法比如单独一个\sqrt预览区直接变空白控制台报一堆红字错误。原因KaTeX默认throwOnError是true遇到无法解析的语法就抛异常异常没有捕获后续渲染逻辑中断整个页面都受影响。解决统一设置throwOnError: false并且在外层用try...catch兜底。别只依赖参数万一渲染引擎内部出别的异常外层的捕获才是最后一道防线。try { katex.render(latex, preview, { throwOnError: false, errorColor: #cc0000 }); } catch (e) { // 兜底任何异常都不允许影响页面其他功能 preview.textContent 公式格式有误请检查; }渲染引擎也是软件也会有bug保持「永远不信任输入的LaTeX」的心态页面才不会崩。5.3 公式里的特殊字符触发了XSS现象用户在公式输入框里写了一段HTML标签或脚本代码渲染后这些代码竟然被浏览器执行了。原因把renderToString返回的HTML直接塞进页面时如果LaTeX里有危险内容比如img onerror...KaTeX默认会转义一部分但不是全部加上某些编辑器锚文本插值写法把HTML原样拼进去了。解决第一层后端保存前做校验只允许以\开头、由合法LaTeX命令组成的白名单结构第二层前端渲染后不要直接innerHTML拼接让KaTeX处理转义不要自己拼HTML片段。// 安全做法把 latex 作为参数传给 renderToString引擎自行处理转义 const html katex.renderToString(userInputLatex, { throwOnError: false, trust: false // 禁止 \html 等危险命令 });第二行的trust: false是关键它会禁用LaTeX里一切能操作HTML的命令比如\href、\htmlId。项目里如果公式不允许外链跳转这个参数建议常开。5.4 编辑器组件重新渲染后预览区的内容丢了现象在React或Vue里公式编辑器的输入框里有内容但组件状态一更新预览区变空了或者渲染滞后一拍。原因框架的DOM更新把预览区节点整个替换了KaTeX渲染进去的公式DOM被清空但textarea的value还在render函数没有重新执行。解决把渲染逻辑放进useEffect或watch里用textarea的value作为依赖项。组件更新后监听器会被重新触发预览区就能同步渲染。// React 伪代码每次 value 变化时重新渲染预览 useEffect(() { if (!previewRef.current) return; katex.render(value, previewRef.current, { displayMode: true, throwOnError: false }); }, [value]);5.5 公式和文字混排时上下被裁剪了一半现象行内公式在段落里显示不全\frac的分子分母超出正文行高被overflow隐藏或者挤压。原因KaTeX的行内公式高度超出了段落行高CSS的line-height和overflow: hidden把公式顶部或底部裁掉了。解决给公式容器单独设置line-height并且不要裁剪溢出。/* 行内公式包裹容器样式 */ .formula-inline { line-height: 1.8; padding: 0.2em 0; overflow: visible; vertical-align: middle; }vertical-align: middle是个容易被忽略的细节否则公式会和文字基线错位。调这个参数没什么标准答案直接改预览区看效果调到对齐顺眼为止。6. 进阶公式输入体验优化与安全验证的实用技巧前面的内容已经把公式编辑器跑通了最后一章分享几个提升体验的技巧。第一个技巧是自动补全。很多用户记不住全部LaTeX命令但记得开头几个字母。常见做法是给textarea增加一个命令联想层检测当前光标前的单词弹出候选命令列表按Tab或回车补全。实现起来不复杂核心就是正则匹配光标前的字符。function getCommandPrefix(textarea) { const beforeCursor textarea.value.slice(0, textarea.selectionStart); const match beforeCursor.match(/\\[a-zA-Z]*$/); return match ? match[0].slice(1) : ; }拿到前缀后从内置命令列表里过滤出匹配项渲染成一个浮层列表。这个技巧对用户体感提升非常明显相当于给公式编辑器装了个输入法。第二个技巧是LaTeX合法性预校验。与其等用户输了半天再报错不如在失焦或敲空格时先检查一遍语法。KaTeX本身自带一个katex.__parse方法但API不太稳定。更稳的做法是渲染前先调用katex.renderToString用throwOnError: false观察是否返回错误节点。如果返回结果里包含katex-error类名就说明语法有问题。function isLatexValid(latex) { if (!latex.trim()) return true; const html katex.renderToString(latex, { throwOnError: false, strict: false }); // 解析失败时 KaTeX 会输出带 katex-error 的节点 return !html.includes(katex-error); }第三个技巧是草稿防丢。公式编辑器输入成本高一次误刷新可能丢半页内容。我习惯在input事件里把LaTeX存进localStorage每次回填时优先读草稿。代码很简单三行就能写完但对用户来说是最实在的「后悔药」。textarea.addEventListener(input, () { localStorage.setItem(formula-draft, textarea.value); }); // 页面加载时恢复草稿 const saved localStorage.getItem(formula-draft); if (saved) textarea.value saved;最后说一个我给自己定的规矩公式编辑器永远不直接信任用户输入后端保存前必须再做一层校验。前端校验防误操作后端校验防恶意数据两层缺一不可。公式编辑器看着简单真正接入生产环境后各种边界情况才会一个个冒出来希望前面这些经验能帮你少踩几个坑。本文还有配套的精品资源点击获取