根本原因是未控制HTML生成路径和清洗时机;应拦截粘贴、输出前用编辑器原生方法或DOMPurify清洗、存储时转Delta/Markdown,三节点缺一不可。

富文本编辑器里输出的 HTML 往往“看着正常,用着崩”,根本原因不是编辑器不给力,而是你没控制住 HTML 的生成路径和清洗时机。直接读 innerHTML、默认接受粘贴、存原始 HTML 到数据库——这三步走完,后续所有格式问题、XSS 风险、跨端渲染异常都只是时间问题。
为什么 innerHTML 不该直接用作输出
浏览器在 contenteditable 区域自动补全标签(比如把孤立 li 塞进 ul),还会悄悄加 class="Apple-style-span"、data-mce-* 、零宽空格(U+200B)等残留。这些内容肉眼不可见,但会导致后端解析失败、移动端换行错乱、搜索关键词匹配丢失。
实操建议:
- 优先调用编辑器原生导出方法:
quill.root.innerHTML是错的,应改用quill.getSemanticHTML()(需引入quill-semantic-html插件)或editor.getData({ trim: 'both' })(CKEditor 5) - 若必须手取,先克隆:
const clone = editorContainer.cloneNode(true),再遍历移除所有非白名单属性(class、style、data-*、id) - 最后过一遍
DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'] }),别跳过
粘贴时 HTML 就该被“截杀”
用户 Ctrl+V 粘贴 Word、微信、网页内容,90% 的格式混乱都发生在这 1 秒内。编辑器默认把原始 HTML 全盘接收,而 Word 的 <o:p>、微信的 span[style*="font-size"]、网页的嵌套 div 根本不是语义结构,只是渲染快照。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- Quill 启用严格粘贴:
clipboard: { matchVisual: false }+ 自定义 matcher 过滤掉font、span[style]、o:p - TinyMCE 设置
paste_as_text: true,或用paste_preprocess钩子做 DOMParser 清洗 - 自己监听
paste事件:event.clipboardData.getData('text/html')拿原始 HTML → 用DOMParser解析 → 白名单保留标签/属性 →insertHTML替代默认行为
存数据别碰原始 HTML
存 <p><strong>Hello</strong></p> 看似省事,但换个编辑器就可能渲染成 <p><b>Hello</b></p>,或者因为自闭合标签(<br> vs <br />)被后端 XML 解析器报错。更麻烦的是:你想加个「高亮」功能,却发现老数据里全是 inline style,没法统一替换。
实操建议:
- 新项目一律存结构化中间格式:
quill.getContents()返回的 Delta 对象(JSON)、lexical的EditorState序列化结果 - 必须存文本?选 Markdown:
remark-rehype+rehype-stringify可控转换,支持自定义节点(如表格、代码块),且天然防 XSS - 已有 HTML 库要迁移?写一次性清洗脚本:用
cheerio把<b>/<i>统一转为<strong>/<em>,删空style、class,再存入新字段
真正难的不是“怎么格式化”,而是“在哪一刻格式化”——粘贴时拦截、输出前清洗、存储前归一,这三个节点漏掉任何一个,HTML 就会从工具变成债务。别指望后期靠正则修复,那只会让问题更隐蔽。



















