纯html编辑(如contenteditable)不能可靠实现富文本,因其缺乏schema约束、跨浏览器兼容性差、粘贴不可控、光标丢失及换行混乱;应选用prosemirror或tiptap等具备文档模型与解析规则的现代方案。

HTML 编辑和富文本不是同一层概念,强行混用必然冲突——不是“能不能一起用”,而是“谁控制语义、谁负责安全、谁定义结构”。
contenteditable + execCommand 为什么算不上富文本
它只是让浏览器临时开启编辑能力,不提供文档模型、不校验结构、不约束标签。比如用户粘贴 Word 内容,contenteditable 会原样吞下 <font color="red"></font>、<span style="mso-...> 这类废弃标签;执行 document.execCommand('bold') 在 Chrome 返回 <b>,在 Safari 可能返回 <strong style=" font-weight: bold></span>,服务端拿到的就是一团不可预测的 HTML 字符串。
- 没有 schema:无法区分“标题”和“加粗段落”,所有都是
<div> 或 <code><p></p> - 无事务机制:撤销/重做依赖浏览器原生实现,跨浏览器行为不一致
- 粘贴不可控:不经过白名单过滤,
<img src="x" onerror="alert(1)">可直接进 DOM -
execCommand已被 MDN 标记为 Deprecated,Chrome 97+ 移除部分命令,Safari 仅保留极少数基础操作 - 前端必须用
DOMPurify.sanitize()做白名单过滤,而非replace(/<script>/g, '')</script>这类正则清洗 - 服务端接收时,不能只解析
req.body.content,要确认请求头是Content-Type: application/x-www-form-urlencoded; charset=UTF-8,且 body-parser 配了encoding: 'utf8' - 数据库字段必须是
utf8mb4,否则 emoji 和数学符号会变??? - 渲染预览时,禁用
innerHTML直接赋值;优先用insertAdjacentHTML('beforeend', sanitized)或服务端转义后以textContent显示纯文本摘要 - 必须监听编辑器的
change、blur或text-change事件,手动同步:textarea.value = editor.getData() - CKEditor 5 推荐用
editor.getData(),不是读editor.element.innerHTML—— 后者不含 widget 渲染结果 - Quill 注意清理尾部空行:
quill.root.innerHTML.replace(/<div> <br>$/i, '') <li>如果用了 <code><form onsubmit="return false"></form>手动提交,确保在 fetch 前已调用同步逻辑,而不是依赖 submit 默认行为
富文本编辑器输出的 HTML 为什么不能直接存、直接渲染
因为编辑器生成的 HTML 是“运行时产物”,不是“存储格式”。CKEditor 5 的 editor.getData() 默认返回带内联样式和 widget 占位符的 HTML(如 <figure class="image"><img src="..."></figure>),Quill 的 quill.root.innerHTML 可能含冗余 <div><br></div> 尾部节点。直接入库或 innerHTML = rawHtml 渲染,会触发 XSS、样式污染、DOM 泄漏等问题。
表单提交时富文本内容丢失的常见原因
这不是编辑器“没保存”,而是 DOM 同步断链。绝大多数富文本编辑器(包括 CKEditor 5、Quill、Tiptap)**不自动绑定到原生 <textarea name="content"></textarea>**。submit 触发时,浏览器只序列化 <textarea></textarea> 的 value,而这个值从没被更新过。
真正难的不是把 HTML 显示出来,而是让每次粘贴、每次撤销、每次提交,都落在同一个可验证、可审计、可回滚的文档模型上。绕开 schema 和 parser 直接操作 HTML 字符串,等于把富文本降级成“带样式的 textarea”。











