纯JavaScript搭建在线公式编辑器:KaTeX实时渲染与LaTeX模板交互实现 简介JavaScript公式编辑器是一套基于HTML5与JavaScript开发的网页数学公式编辑工具源码面向网页开发者、在线教育内容制作者和理工科教学人员用于在浏览器中直接录入、编辑并展示复杂公式既能满足在线课件、题库系统中的公式录入需求也适合作为前端交互项目的起步模板。压缩包共2个文件包含1个JS核心脚本与1个HTML示例页面JS集中处理公式解析、事件监听与渲染逻辑HTML负责搭建编辑器界面和显示区域整体仅9KB结构紧凑便于直接运行或嵌入现有网页打开示例页面即可体验编辑器效果方便快速验证。当前已有1309人学习下载。源码规模虽小却覆盖了公式解析LaTeX/KaTeX渲染思路、函数输入与图像绘制、实时预览等关键模块并通过代码展示了文档对象模型DOM操作、事件监听、Canvas/SVG绘图以及跨浏览器兼容处理等方法。借助这份精简实现读者既能理解公式编辑器从界面布局到渲染交互的完整链路也能借鉴其模块划分与交互设计用于二次开发、课程设计或日常科研中的公式录入场景。 公式编辑器这种东西看着简单真做起来全是细节。我最近用纯JavaScript给在线题库系统做了一个网页版公式编辑器核心目标是解决用户录入数学公式的痛点——普通人不熟悉LaTeX语法直接甩一个textarea让人写\frac这种代码根本不现实。最终做出的是“左侧源码输入 右侧实时预览 模板按钮面板”的完整交互闭环UI能融入现有系统输出的是标准LaTeX字符串存储、传输、二次编辑都方便。项目不依赖React/Vue这类框架核心渲染用KaTeX纯前端就能跑也方便离线部署。如果你正好在做在线教育、文档协作、考试系统或者只是博客里缺一个公式输入工具下面这套思路和代码可以直接参考。1. 项目整体设计与思路拆解1.1 与其套用现成编辑器不如自己搭一套在动手写代码之前我先把市面上现成的方案翻了一遍。MathQuill这种重量级公式编辑器确实是标杆但真塞进项目里才发现问题不少API偏底层、定制样式要覆盖一堆内部结构、和现有表单体系比如二次编辑回显、校验规则结合成本很高多数时候为了适配它的交互逻辑反而要改自己业务侧的东西。MathType在线版功能更强但它是商业服务数据要过第三方接口在线使用时还担心加载速度和离线场景。评估完之后我意识到大部分业务系统真正需要的其实不是“一个数学软件”而是“一个支持数学公式输入的表单控件”用户录完公式系统存一个标准LaTeX字符串下次打开再渲染回显仅此而已。这个闭环用“textarea 渲染库”自己写是完全可控的。1.2 编辑器整体架构输入层、逻辑层、渲染层怎么分工整个编辑器我拆成了三层层与层之间边界清晰调试起来很省心。输入层负责跟用户打交道包括textarea源码输入区、模板按钮面板、快捷键响应逻辑层是整个项目的大脑负责监听用户输入、把模板按钮映射成LaTeX片段、维护光标位置、处理选区替换渲染层只做一件事就是把LaTeX字符串交给KaTeX换成能在页面里展示的HTML结构。设计上我坚持一条原则数据单向流动。textarea里的LaTeX字符串是唯一数据源任何操作键盘输入、点按钮、快捷键最终都落到“修改这段字符串”上然后再触发渲染刷新。这样做的好处是状态永远可预测不会出现输入框内容和预览区不一致的诡异情况。就算以后要迁移到Vue或者React把textarea换成受控组件数据流模型也完全不用改。这也是为什么后面写代码的时候我大量封装纯函数操作逻辑不掺杂任何UI耦合。1.3 功能边界哪些做、哪些一定不做做编辑器最容易失控一开始我列的需求清单非常宏大矩阵拖拽、鼠标框选、公式内光标定位全都想塞进去后来冷静了一下全砍了。MVP阶段保留了四个核心能力模板按钮分式、上下标、根号、积分、求和、希腊字母、矩阵环境、实时预览、快捷键、以及LaTeX导出。像MathQuill那种鼠标点进公式某个结构继续编辑的体验属于“高级增强”短期性价比太低放到二期再评估。公式OCR识别拍照录入这种跟编辑器本身关系不大也不归这个项目管。边界划清楚之后开发节奏快很多真正核心的代码量其实不大精力都花在了体验细节而非功能堆叠上。2. 核心细节解析与实操要点2.1 KaTeX还是MathJax一个表格讲清楚渲染层是这个项目最关键的技术选型。公式渲染基本就是KaTeX和MathJax二选一两者都支持LaTeX语法但侧重点有明显区别。对比维度KaTeXMathJax渲染速度快几百个公式也不怎么卡相对慢公式数量大时有明显延迟LaTeX兼容范围主流公式够用个别冷门宏不支持更全老式命令和论文级公式兼容更好打包与资源体积相对小CSS自包含更大还要配字体资源离线部署简单静态资源引入即可相对麻烦字体路径没配好容易乱码维护方Khan Academy学术圈广泛使用的MathJax Consortium典型场景实时预览、高并发、前端嵌入arXiv、论文网站、复杂数学排版我最终选KaTeX核心原因是题库和教学场景的公式其实都是中小学到大学范围内的标准公式KaTeX覆盖得足够完整而实时预览这里“敲一个字符马上出效果”对渲染速度要求很高KaTeX明显比MathJax跟手。再一个就是KaTeX渲染结果是一段自包含的HTML字符串方便我复制到别的页面或者套进邮件模板里。如果你做的是学术论文排版那种冷门符号满天飞的系统那就老实上MathJax没必要硬扛兼容性。2.2 模板按钮设计把LaTeX封装成“点一下就能用”模板按钮层是影响用户好感度的重中之重。我的设计思路是把常用LaTeX结构做成带占位符的模板用户点一下按钮模板插到textarea的光标位置选中内容可以自动填进第一个空位。这个交互看起来简单但细节都在模板的组织方式上。每个模板本质上就是一段带标记的字符串const templateMap { frac: \\frac{$}{}, sqrt: \\sqrt{$}, sum: \\sum_{$}^{}, int: \\int_{$}^{}, sin: \\sin($), pmatrix: \\begin{pmatrix} $ \\end{pmatrix}, aligned: \\begin{aligned} $ \\end{aligned} };我用$作为“当前选中内容要插入的位置”模板插入时先检测用户有没有选中文字。比如用户选中了x1再点“分数”按钮得到的就是\frac{x1}{}光标自动停在分母位置等用户补充。没有选中内容时直接插入\frac{}{}并让光标落在分子位置。分组也要做基础运算、上下标、希腊字母、矩阵分别收纳到不同折叠面板里避免按钮一长串刷屏。2.3 光标与选区最容易翻车的交互细节插入模板之后光标落在哪里这是很多半成品编辑器翻车的地方。直接对textarea的value做字符串拼接再赋回去光标百分之百会跳到末尾用户每点一次按钮就要手动点回去体验直接劝退。用selectionStart和selectionEnd可以精确控制光标位置。插入模板前先记录光标位置和选中范围插入后根据模板中的占位符位置重新设置光标。多一个占位符的模板处理起来会复杂不少所以我先在MVP阶段统一成“一个占位符 光标停在模板末尾”保证最常见场景是顺手的。还有一个隐藏要求处理选区时如果用户选中的内容本身已经是一个完整公式比如\frac{a}{b}再点分式按钮时不希望它被拆散所以模板插入逻辑还要考虑不要破坏已有结构。这里我的做法是先用分组匹配判断选中内容是否已经是LaTeX环境是就直接包一层不是才做简单替换。3. 实操过程与核心功能实现3.1 渲染环境搭建三步跑通KaTeX先搭一个最小可运行环境。KaTeX官方提供了CDN和npm包两种方式npm适合打包工具项目如果要快速验证直接用CDN引入就行。但正式部署一定要把CSS、JS和字体文件下载到本地放到项目的静态资源目录里避免外链不稳定也保证内网离线可用。基础引入代码link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/katex0.16.11/dist/katex.min.css script srchttps://cdn.jsdelivr.net/npm/katex0.16.11/dist/katex.min.js/script初始化一个通用的渲染函数后续所有预览、回显都走它function renderFormula(latex, containerId) { const target document.getElementById(containerId); if (!target) return; if (!latex) { target.innerHTML ; return; } katex.render(latex, target, { throwOnError: false, displayMode: true }); }displayMode: true对应块级公式居中对放适合预览区throwOnError: false的作用是渲染出错时不抛异常打断页面后面我会专门讲这个配置的坑。3.2 核心逻辑插入模板、实时预览、快捷键页面结构分成三块左边的textarea负责源码输入中间的按钮面板负责模板插入右边的div负责实时预览。核心交互代码集中在两个函数里insertTemplate负责处理模板插入和光标定位updatePreview负责防抖渲染。const latexInput document.getElementById(latex-input); const previewBox document.getElementById(preview); function updatePreview() { const latex latexInput.value || ; renderFormula(latex, preview); } function insertTemplate(template) { const start latexInput.selectionStart; const end latexInput.selectionEnd; const currentValue latexInput.value; const selectedText currentValue.substring(start, end); const newText template.replace($, selectedText || ); const nextValue currentValue.substring(0, start) newText currentValue.substring(end); latexInput.value nextValue; const cursorPos start newText.length; latexInput.focus(); latexInput.setSelectionRange(cursorPos, cursorPos); updatePreview(); }选中已有公式后点模板选中内容会被保留并放入新模板的占位符里光标挪到末尾用户可以直接继续敲。实时预览不能每次键盘都全量渲染我加了一个200毫秒的防抖停下输入后才刷新输入过程中连续敲键盘不会卡顿。快捷键这块我监听了textarea上的Tab键按下时自动补全\frac{}{}对习惯LaTeX的进阶用户来说效率提升很明显。3.3 换行与连等号公式排版的一个隐藏需求项目上线后收到最多的反馈居然是公式换行问题用户问“一个推导过程怎么换行对齐”这个需求比我想象中普遍。LaTeX里多行公式要用aligned环境连等号场景需要配合对齐符和换行符\begin{aligned} a^2 1 \\ a \pm 1 \end{aligned}这里表示“等号处对齐”\\是换行。很多用户不知道这个语法也记不住所以我把“多行对齐”做成一个单独的按钮点一下直接插入完整的aligned环境模板用户只需要往里填公式、用标注对齐位置、用\\换行。单行公式和多行环境全部由编辑器统一管理有效避免用户手写时漏了\\导致整个公式渲染错乱。这个细节补上之后公式编辑器的实用性提升非常明显强烈建议你保留。4. 常见问题与排查技巧实录4.1 KaTeX渲染异常别把错误丢给throwOnError写公式卡最频繁的坑就是渲染报错。KaTeX默认遇到不认识的命令或者不完整的语法会直接抛异常渲染中断页面被一堆报错信息淹没。我第一版也是直接调用katex.render结果用户输了个\frac{还没闭括号预览区整个崩掉。后来统一加上throwOnError: false让单条公式出错时只在该公式位置显示红色内容不影响整体页面。但这个配置也有副作用错误会被静默吞掉用户不知道哪里写错了。我的处理方式是给textarea下方加一个“语法检查”提示区每次渲染前先用一个轻量的LaTeX括号匹配检查把不完整公式的位置标出来提示用户“可能缺少右括号”。对业务系统来说这比满屏红色错误码友好得多。4.2 输入框的光标问题与中文输入法插入模板后光标跳到末尾这个问题在引入setSelectionRange之后解决了但中文输入法的问题一直隐蔽得很。用户在用拼音输入法打字时textarea会触发compositionstart事件此时input事件里的value包含正在合成的拼音如果这个时候去渲染预览会出现公式闪烁、光标跳动甚至拼音字母被当成LaTeX代码塞进去。我的解决办法是维护一个composing标记合成期间不触发渲染等compositionend之后再做一次完整预览。这一个小改动对中文用户来说体验差别非常大。let composing false; latexInput.addEventListener(compositionstart, () { composing true; }); latexInput.addEventListener(compositionend, () { composing false; updatePreview(); }); latexInput.addEventListener(input, () { if (!composing) updatePreview(); });4.3 弹窗与异步场景DOM还没渲染完就渲染公式第二个高频问题出现在把编辑器嵌进Modal弹窗的时候。弹窗打开编辑器DOM还没完全挂载这时候调用渲染函数拿到的目标容器是null或者宽度是0KaTeX渲染出来的公式整体错位。排查发现是弹窗的进场动画和DOM挂载时机的问题页面在所有容器渲染完成之前就跑了初始化脚本。解决思路很直接弹窗打开事件回调里等过渡动画结束再初始化编辑器和渲染或者在弹窗的opened钩子不同框架名字略有差异里做初始化。另一个相关的坑是弹窗尺寸变化。KaTeX公式宽度是基于容器宽度计算的一旦弹窗被拖拽拉宽或拉窄公式不会自动适应。我的做法是监听窗口或容器的resize事件触发一次重新渲染这个逻辑在响应式布局下效果尤其重要。4.4 性能优化缓存、防抖与按需渲染公式数量多起来之后实时预览卡顿就出现了。题库列表页动辄渲染几百条公式每次切页都要全部重新解析一遍。我做了三个优化实测下来效果明显。第一层是输入防抖这是最基础的第二层是用Map做渲染结果缓存以LaTeX字符串为key同一个公式第二次出现时直接复用之前渲染好的HTML不重新调用KaTeX第三层是懒渲染页面滚动时只渲染进入视口范围内的公式用IntersectionObserver做首屏速度大幅提升。实际跑下来的数据题库列表几百个公式从最初约1.2秒首屏优化后降到400毫秒左右滚动过程基本没有明显卡顿。这个优化幅度对中小系统足够用了没有必要再上Web Worker之类的重型手段。做这个编辑器最大的收获是别被“公式编辑器”这个词吓住。大部分业务并不需要MathQuill级别的交互一个textarea加一个渲染库就能解决80%的录入、回显、导出的需求。我建议你先把基础闭环跑通再把精力花在模板、光标、错误提示这些细节打磨上这套组合是性价比最高的路子。最后分享一个习惯写公式编辑器时一定准备一组覆盖常见公式的测试用例分数套根号、多行对齐、矩阵、带下标的极限每次改完核心代码跑一遍能少踩很多回归的坑。本文还有配套的精品资源点击获取