☰
UEditor集成LaTeX公式插件:富文本编辑器的数学排版方案
2026/10/25 11:50:10 网站建设 项目流程

简介:面向需要在网站或后台系统中集成富文本编辑器并支持数学公式输入的开发者,这份资源将UEditor与KityFormula数学公式插件完整打包,适用于在线教育、技术博客、试题系统等场景。压缩包内预置了公式排版所需的按钮图标、样式文件与交互脚本,可直接部署或参照集成,减少环境配置与图片资源缺失等常见问题。资源共747个文件,以JavaScript脚本、CSS样式、PNG图标、GIF演示素材为主,另含HTML示例页与少量字体(WOFF、TTF)、SWF等兼容文件,压缩包整体仅3.43MB,部署轻量、加载成本低。已有1463人在CSDN学习或下载,具备一定参考热度,说明该整合方案能解决不少开发者的实际需求。通过随包示例与对应博文说明,读者可以拿到完整的编辑器前端资源、公式插件调用逻辑、样式适配方案及目录结构整理,既能直接部署使用,也能参照其结构进行二次开发,快速搭建出支持数学公式输入的UEditor编辑器。

1. 带公式插件的 UEditor 编辑器:先解决三个问题再谈交付

我在做一个在线题库项目时,甲方明确提了一个需求:富文本里要能写带积分号、矩阵、希腊字母的数学公式。默认的 UEditor 只支持文字和图片,公式要么截图上传,要么手写 LaTeX 再贴进来,体验差、存储也乱。这份资源就是把公式插件集成好的 UEditor 编辑器,点工具栏里的公式按钮,弹出输入面板,敲 LaTeX 语法就能实时预览,插入正文后在前端直接渲染成公式。它能解决论文、题库、课件这类内容里公式录入、存储和回显的完整链路,适合正在做教育平台、知识库、OA 表单或任何需要数学排版系统的开发者。我花了一个下午把它跑通,顺便把几个常见的坑记了下来,这篇就围绕集成、数据流和避坑写清楚。

2. 集成方式与内部结构:先把环境跑通,再谈改代码

2.1 插件注册原理:UEditor 的 dialog 机制和公式渲染两条路

UEditor 的插件体系并不复杂,核心是UE.plugin.register注册插件对象,插件内部通过editor.ui._dialogs挂载对话框。公式插件本质上就是一个自定义 dialog,里面嵌一个 LaTeX 输入框和一个数学渲染区。输入 LaTeX 语法,触发预览,点确定后把内容以文本标记的形式插回编辑区。

这里有个关键分支:公式渲染到底用哪条路。常见做法有两种。一种是后端渲染成图片,把<img src="...">插入正文,好处是兼容性好,老浏览器也认识,坏处是缩放容易糊、修改公式要重新生成图片、存储体积大。另一种是保留 LaTeX 文本,前端用 MathJax 或 KaTeX 渲染,坏处是依赖前端脚本,好处是可编辑性好、文本存储。

这份资源里走的是第二种路线,而且把预览和渲染都放在了前端。这样做对项目最直接的好处是:数据库里只存$x^2 + y^2 = z^2$这样的文本,回显时用渲染器处理,公式不会因为图片丢失而变成红叉。

安装后的目录结构大致是这样:

ueditor/ ├── ueditor.config.js ├── ueditor.all.js ├── lang/ │ └── zh-cn.js ├── dialogs/ │ ├── formula/ │ │ ├── dialog.js │ │ └── dialog.html │ └── ... └── third-party/ └── mathjax/

dialogs/formula/就是公式插件本体,dialog.js 负责绑定事件和取值,dialog.html 负责界面布局,MathJax 放在third-party/下统一管理。我一般会把整个dialogs/formula目录连同 MathJax 目录一起拷进目标项目,避免只拷贝一部分导致按钮点了没反应。

2.2 引入项目的具体步骤:配置项和初始化代码

第一步是调整工具栏。UEditor 的工具栏按钮由toolbars配置决定,没有把按钮加进配置,即使插件文件已经加载,界面上也不会出现入口。

var ue = UE.getEditor('container', { toolbars: [ [ 'fullscreen', 'source', 'undo', 'redo', 'bold', 'italic', 'formula', 'insertimage', 'link' ] ], autoHeightEnabled: true, autoFloatEnabled: true, initialFrameHeight: 400 });

这段配置里formula是公式按钮的唯一标识,必须和插件里注册的按钮名完全一致。initialFrameHeight控制编辑区初始高度,autoHeightEnabled打开后内容增多时编辑区会自动拉伸,适合题库录入场景。autoFloatEnabled决定了工具栏是否随滚动吸顶,多表单页面建议关闭。

第二步是调整过滤规则。UEditor 默认的 xss 过滤规则会把某些标签和属性清掉,公式插件的渲染结果如果用了特殊 class 或 data 属性,有可能被过滤掉,导致前端展示异常。

UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl; UE.Editor.prototype.getActionUrl = function(action) { if (action === 'catchimage') { return '/api/upload/catchimage'; } return this._bkGetActionUrl.call(this, action); };

这段代码是处理远程图片抓取的,但在公式插件的场景里,重点不是这个,而是下面要说的:在ueditor.config.js里调整过滤白名单。公式插件的 dialog 输出的是带class="formula"的span或者p标签,默认的过滤规则并不一定会保留自定义 class,所以要在ueditor.config.js里配置:

UE.registerUI('formula', function(editor, uiName) { editor.registerCommand('formula', { execCommand: function() { var dialog = new UE.ui.Dialog({ iframeUrl: editor.options.UEDITOR_HOME_URL + 'dialogs/formula/dialog.html', editor: editor, name: 'formula', title: '插入公式', cssRules: 'width:620px;height:400px;' }); dialog.open(); } }); });

这里做了两件事:注册formula命令,以及指定 dialog 的 iframe 地址。UEDITOR_HOME_URL是编辑器资源目录的根路径,必须指向实际部署的路径,否则 dialog 页面加载不到,表现为点了按钮没有弹窗。cssRules控制弹窗尺寸,公式面板通常比普通弹窗要宽,620px 是我测试下来比较常用的宽度。

2.3 和 Markdown 编辑器的边界:什么时候别用它

收到这个资源后,我先看的是它的可替代性。说实话,如果你只是写博客或者内部文档,Markdown 编辑器配合 KaTeX 可能更轻量。但如果项目里已经有大量老内容、后端有几个旧接口依赖 UEditor 的图片上传回调,那么替换成 Markdown 编辑器的工作量远大于集成公式插件。我的判断标准是看存量内容:如果现有数据库里已经是 UEditor 输出的 HTML,就地加公式插件是性价比最高的方案;如果是从零做新站点,再考虑 Markdown 系编辑器。

这里有一个容易混淆的点:公式插件的存储内容仍是 HTML 片段,而不是 LaTeX 源码。也就是说,公式插件解决了「录入和渲染」的问题,并没有把整个编辑器变成 LaTeX 源码编辑器。你把公式插入正文后,editor.getContent()返回的是类似<span class="formula">$x^2$</span>的 HTML。这一点直接决定了后端存储字段的类型和回显方式,后面第 3 章会展开细节。

3. 公式数据链路:从点击按钮到入库,每一步都搞清楚

3.1 前端面板交互和 LaTeX 实时预览的机制

公式插件的前端交互核心是一个输入框加一个渲染区。用户在输入框里敲\sqrt{x},dialog.js 监听 input 事件,把当前内容交给 MathJax 渲染,再将渲染后的 HTML 填充到预览容器。整个过程是纯前端的,没有走 ajax,所以离线环境也能用。

预览渲染这一块,我先看插件的实际实现,如果预览刷新有延迟,通常是因为 MathJax 渲染是异步的。常见做法是每次输入后先清空预览容器,再把原始的 LaTeX 文本赋值给容器,最后调用MathJax.Hub.Queue(["Typeset", MathJax.Hub, previewElement])让 MathJax 重新排版。

var inputBox = document.getElementById('latex-input'); var previewBox = document.getElementById('formula-preview'); inputBox.addEventListener('input', function() { var latex = inputBox.value.trim(); if (!latex) { previewBox.innerHTML = ''; return; } previewBox.innerHTML = '\\(' + latex + '\\)'; if (window.MathJax && MathJax.Hub) { MathJax.Hub.Queue(['Typeset', MathJax.Hub, previewBox]); } });

这段代码里有一个容易被忽略的点:previewBox.innerHTML = '\\(' + latex + '\\)'里的\\(和\\)是 LaTeX 的行内公式定界符,不是正则表达式的括号。MathJax 靠这对定界符判断「哪一段是公式」,如果少了定界符,MathJax 会把内容当普通文本处理,预览区显示的就是x^2而不是 $x^2$。Typeset是 MathJax 的排版队列方法,把previewBox传进去,意思是重新渲染这个容器内的全部数学公式。

这里有一个性能玄学:渲染大矩阵时,MathJax 在多行复杂公式下会明显卡顿,尤其是在老机器上。我实际测试过一个 5 行 5 列的矩阵,Preview 区域刷新要几百毫秒,如果每秒触发的 input 事件太多,整个页面会看起来像卡死。常见的做法是加一个 300ms 的防抖。

3.2 插入编辑器的内容格式与存储设计

当用户点确定时,dialog.js 会把 LaTeX 文本包上一层自定义标签,然后调用editor.execCommand('insertHTML', html)插入正文。这个 HTML 里保留了 LaTeX 原始文本,同时也带上class="formula"方便回显时统一识别。

var latex = inputBox.value.trim(); var html = '<span class="formula"><script src="/libs/ueditor/ueditor.config.js"></script> <script src="/libs/ueditor/ueditor.all.js"></script> <script src="/libs/ueditor/lang/zh-cn/zh-cn.js"></script>

这里的坑在于,zh-cn.js语言包必须在ueditor.all.js之后加载,因为它会往 UEditor 内部挂中文文案。如果顺序反了,界面文字可能部分显示为英文,部分为 undefined。另外,ueditor.config.js必须在最前面,因为它给后续加载的脚本提供全局配置对象。

问题二:公式插入后正文里能看到 LaTeX 源码,但页面不渲染

现象:点确定后,编辑区显示\(\sqrt{x^2+1}\)这样的字符串,没有渲染成公式。

原因:会话场景中 UEditor 初始化后,MathJax 没有对编辑器容器做一次全量排版。公式插件只在预览时调用 MathJax,插入后如果编辑区内已经渲染过的容器没有被重新扫描,MathJax 就找不到新的 LaTeX 内容。

解决:插入后手动调用一次 MathJax 排版。

editor.addListener('contentchange', function() { if (window.MathJax && MathJax.Hub) { MathJax.Hub.Queue(['Typeset', MathJax.Hub, editor.document.body]); } });

contentchange是 UEditor 自带的事件,每次内容发生变化都会触发。我把它绑在这个事件上,而不是直接绑到「确定」按钮,是为了覆盖粘贴、撤销、重做等所有内容变更路径。editor.document.body是当前编辑器的 body 节点,直接传整个 body 给 MathJax,保证新插入的公式全部被处理。这个做法有一个小开销:大文档中触发频率较高,如果后期性能吃紧,可以改成带防抖的处理。

问题三:公式显示成图片,放大后模糊

现象:前端正确显示了公式,但看起来是图片,浏览器缩放后发虚,而且复制公式文本到别处不再可编辑。

原因:部分老版本的公式插件为了兼容 IE8 或者没有引入 MathJax,兜底逻辑是把公式渲染成 base64 图片插入正文。这个方案做兜底确实稳,但代价就是公式不再可编辑,缩放发虚,存储体积也大。

解决:确认引入的版本是走文本渲染。如果项目必须兼容老浏览器,那么在页面加载 MathJax 前先做能力检测,只有支持canvas的浏览器才允许公式插件启用文本渲染模式,否则回到图片模式。我一般把 MathJax v2 作为默认渲染器,v3 的配置和 v2 不兼容,混用会报 TypeError,所以换版本时记得同步改渲染调用代码。

问题四:同一页面放多个富文本编辑器,只有第一个的公式按钮能用

现象:一个订单页放了三个编辑器,第一个正常,第二个、第三个点公式按钮全部无反应,或者弹出来的 dialog 跑到第一个编辑器里去。

原因:UEditor 的 dialog 是全局单例的,UE.ui.Dialog在同一时刻只维护一个实例。如果多个编辑器实例共用同一个 dialog 资源,dialog 内部的editor引用是指向第一个实例的,后续实例的插入操作就全部指向了错的编辑器。

解决:在registerCommand里每次打开对话框时,显式传入当前的编辑器实例,不要用闭包缓存外部editor变量。我踩过一次,当时为了省代码直接把外层变量拿来用,结果第三个编辑器的公式插入到了第一个编辑器的内容区里,找了一个多小时才发现是闭包引用问题。

editor.registerCommand('formula', { execCommand: function() { var currentEditor = this; var dialog = new UE.ui.Dialog({ iframeUrl: editor.options.UEDITOR_HOME_URL + 'dialogs/formula/dialog.html', editor: currentEditor, name: 'formula', title: '插入公式', cssRules: 'width:620px;height:400px;' }); dialog.open(); } });

this在execCommand的执行上下文里就是当前编辑器实例,这一点是 UEditor 内部约定。传currentEditor而不是外部的editor,能保证多实例下 dialog 持有的引用是当前的这一个。

问题五:公式单独看正常,但整篇回显时和文字挤在一起

现象:公式插入时排版正常,保存后重新加载页面,公式和上下文的行距挤压,或者公式比文字高出半个身位。

原因:行内公式默认是inline-block或inline渲染,如果带上vertical-align默认值,在一段中文文字里容易造成基线错位。这本质是 CSS 层面的事情,与 LaTeX 内容本身无关。

解决:给公式所在标签加统一样式,把vertical-align调整为middle,并留出一个足够的高度让公式显示完整。

.ueditor-content .formula { display: inline-block; vertical-align: middle; margin: 0 4px; line-height: 1.6; }

margin: 0 4px是让公式和两侧文字之间留一点间距,否则公式和汉字贴着会很难看。line-height: 1.6是给公式一个比正文更高的行高,避免行内公式的下标部分被行框切掉。如果你用的小米浏览器或者某些老版 Chrome,还需要给.formula加一个min-height,不然\frac{}{}这类分数结构会被裁掉上半部分。

5. 验证清单与二次扩展:让公式插件稳定跑在正式环境里

拿到这份资源并完成集成后,我建议你别急着包一层接口就上线。我做项目总结了一份公式插件的验证清单,每一条都有明确操作和预期结果,直接照着过一遍最省事:

验证项操作方式预期结果
初始化渲染加载页面,等待 2 秒编辑器渲染完整,公式按钮图标正常显示
公式预览点公式按钮,输入\int_a^b x^2 dx预览区实时显示积分公式,无报错
插入回显点确定,编辑区出现公式编辑区公式与预览一致,光标位置正确
保存回读取editor.getContent(),走后端接口再渲染新页面展示公式正常,无 LaTeX 源文本泄漏
多实例测试同页面初始化两个编辑器,分别插公式两个编辑器各自独立,不串内容
内容重复编辑对已插入的公式再次选中并修改公式文字可编辑,修改后重新渲染

最后一栏「内容重复编辑」很多项目会漏掉:公式插入后是一个 HTML 片段,你要确认点击公式后光标能定位到 LaTeX 文本,并且能直接删改。如果公式变成了「不可编辑的块」,对内容运营人员来说体验很差。

二次扩展方面,我一般会在 dialog 里再加一个「常用公式」面板,点选直接插入。实现思路是在公式输入框上方放一组快捷按钮,按钮绑定的 LaTeX 模板可以写死在 dialog.js 里:

var quickList = [ { name: '根号', latex: '\\sqrt{x}' }, { name: '分数', latex: '\\frac{a}{b}' }, { name: '积分', latex: '\\int_{a}^{b} f(x) dx' }, { name: '求和', latex: '\\sum_{i=1}^{n} i' } ]; quickList.forEach(function(item) { var btn = document.createElement('button'); btn.textContent = item.name; btn.addEventListener('click', function() { inputBox.value += item.latex; inputBox.dispatchEvent(new Event('input')); }); document.getElementById('quick-panel').appendChild(btn); });

inputBox.dispatchEvent(new Event('input'))这一行是利用的事件冒泡来触发前面的预览逻辑,这样点击快捷按钮后不需要单独调一遍渲染函数。inputBox.value +=是把模板追加到已有内容末尾,如果你想实现光标位置插入,可以改用setRangeText,不过模板尾部追加对大多数场景已经够用了。

这个资源我在实际项目中已经跑过两个版本了,那次多实例串内容的惨案之后,我每次集成 UEditor 插件都会强制走一遍「工具栏按钮确认 → 过滤规则检查 → 多实例冒烟」这三板斧,公式插件也不例外。这个习惯帮我避免了至少三次线上咨询的尴尬,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询