高性能富文本编辑器必须放弃纯HTML模板驱动,转为基于虚拟DOM或节点树的响应式控制,因contenteditable无法解决光标同步、格式隔离、增量更新等核心问题。

直接用 contenteditable + 原生 HTML 模板拼接,做不出高性能富文本编辑组件——模板只是静态结构,而富文本的核心是动态 DOM 状态管理、光标同步、格式隔离和增量更新。真要“高性能”,必须放弃纯模板驱动,转为基于虚拟 DOM 或节点树的响应式控制。
为什么 HTML 模板无法支撑富文本的实时交互
HTML 模板(比如 <div contenteditable="true"></div> 配合预设 toolbar 按钮)只解决“显示”问题,不解决以下关键行为:
-
contenteditable元素内光标位置无法被模板描述或预测,每次输入/粘贴后 DOM 结构可能突变,导致模板重渲染失焦 - 模板中写死的
<strong>或<ul>标签,无法响应用户选区变化自动高亮对应 toolbar 按钮(即document.queryCommandState('bold')在现代浏览器中已不可靠) - 粘贴 Word 内容时,模板生成的初始结构会被浏览器直接注入一堆嵌套
<div class="MsoNormal">,模板无法拦截或清洗 - 表格、代码块、图片等复杂节点一旦用模板硬编码,就失去可编辑性——
<td>里没设contenteditable="true",点进去根本不能输字
真正可用的“模板”应是声明式节点定义,不是 HTML 字符串
高性能组件(如 Tiptap、ProseMirror、mp-html 的编辑模式)从不依赖 innerHTML 拼接模板,而是把“模板”理解为节点 schema:
- 定义合法节点类型:比如
paragraph、heading、image、table,每个类型带自己的属性约束(如image必须有src和alt) - 用 JSON 描述初始内容,而非 HTML 字符串:
{ type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }] } - 渲染层按 schema 映射到真实 DOM,但所有变更都走 transaction 提交,而非
innerHTML = newHtml - 这样做的好处:撤销/重做可序列化、协作编辑能 diff 节点差异、移动端光标不会因整段替换而跳失
如果非要从 HTML 字符串启动,必须做三件事
某些场景(如 CMS 导入旧文章)确实需要从 HTML 初始化编辑器。此时不能直接 editor.innerHTML = htmlStr,否则性能崩、状态丢、样式乱:
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
立即学习“前端免费学习笔记(深入)”;
- 先用
DOMParser解析 HTML 字符串,再递归遍历清洗:删空<span>、合并相邻文本节点、标准化<b>→<strong>、补全缺失的<tbody> - 禁止直接插入含
style属性的节点;统一走编辑器内置的 mark 类型(如colormark),由 theme CSS 控制渲染 - 插入后调用
editor.commands.setContent()(Tiptap)或view.dispatch(tr)(ProseMirror),触发完整状态同步,而不是 DOM 替换
mp-html 的编辑能力其实不推荐用于生产编辑场景
虽然 mp-html 支持“编辑模式”,但它本质仍是渲染器扩展,不是编辑器内核:
- 它没有 selection tracking,iOS 上长按选词后点击加粗,经常作用在错误位置
- 不支持撤销栈,
history.back()会丢失整个编辑状态 - 表格编辑仅限单元格内文字,无法增删行列、合并单元格——这些操作需要 schema-aware 的 command,不是 HTML 模板能承载的
- 它的优势在「渲染」:解析 HTML → 小程序组件树 → 高性能 diff 更新;编辑只是附带功能,别当主力用
真正卡住性能的从来不是“怎么画按钮”,而是“光标在第 3 行第 5 个字符时,按 Ctrl+B,该不该把整个 paragraph 重新 render”。这一步没走对,模板再漂亮也白搭。


















