在讨论富文本编辑器选型时,许多开发团队的第一反应往往是“直接用 contenteditable 不就行了?省去集成第三方库的麻烦”。但真正实践过的人都知道,这条捷径远没有表面看起来那么顺畅。光是基础粘贴行为就层出不穷:从 Word 复制过来的内容常常包含大量嵌套的 ,瞬间冲垮页面原有的样式体系;Firefox 和 Chrome 对 Enter 键生成的究竟是 还是 也各持己见,无法统一。更令人头疼的是,document.execCommand 已被官方明确废弃,其行为变得极度不可预测。光标定位、撤销重做这些看似基础的交互,在 contenteditable 中几乎无法可靠控制——你无法保证精准地将光标置于加粗文字的中间位置再按下 B 键,连续按三次 Ctrl+Z 后光标落在哪里也完全成了玄学。简单来说:能打字并不等于能交付。

为什么不能直接用 contenteditable 替代富文本编辑器
它连最基本的粘贴行为都无法掌控:从 Word 粘贴进来的一堆嵌套 直接破坏页面布局,Firefox 和 Chrome 对 Enter 键生成的标签类型( 还是 )存在分歧,document.execCommand 已被废弃且行为随机性极大。更加致命的是——你无法可靠地将光标定位到加粗文字的中间再按 B 键,连续执行三次 Ctrl+Z 后光标位置也无法预知。所谓“能打字”离“能交付”还差得很远。
TinyMCE 和 CKEditor 5 在企业项目里怎么选
关键取决于团队对可控性的接受程度:
TinyMCE:适合已有后端上传逻辑、只需要快速集成的场景。它的images_upload_handler能直接接收blob,返回 URL 后自动插入;但插件生态比较分散,升级时容易因为某个第三方插件导致构建中断。CKEditor 5:必须使用ClassicEditor.create()方法,不能直接用InlineEditor(后者不会绑定表单);默认输出 HTML,但如果后端只读取req.body.content,而前端没有同步值或者没有设置name属性,就会丢失格式;模块化程度高,但自定义工具栏时需要编写config.toolbar.items数组,不能直接用字符串配置。
图片上传失败的三个高频原因
很多时候问题并非出在编辑器本身,而是链路在中间层断开了:
- Quill 默认会将图片转为
data:image/png;base64直接嵌入 HTML —— 后端通常拒绝接收,因为体积庞大、缺乏防盗链机制、无法通过 CDN 缓存。 - CKEditor 5 的
SimpleUploadAdapter配置中遗漏了uploadUrl,或者响应没有返回{ default: "https://..." }这样的标准结构。 - TinyMCE 的
images_upload_handler函数里忘了调用success(uploadUrl),或者虽然使用了failure(errorMessage)但没有 catch 错误,导致静默失败。
后端接收 HTML 内容时最容易忽略的点
前端传递的是 HTML 字符串,而后端默认可能只解析 application/x-www-form-urlencoded 或 multipart/form-data 中的文本字段,却忽略了 text/html 类型。具体来看:
- Express 用户需要调整
body-parser配置:加上type: 'text/*',否则req.body.content会是空值。 - Spring Boot 用户需要确认
@RequestBody String content能够正常接收,而不是被 Jackson 当作 JSON 解析时报错。 - 所有后端都应该进行 XSS 过滤 —— 不要直接存储
editor.getData()返回的原始 HTML,务必先经过DOMPurify.sanitize()处理。
实际维护中最难啃的骨头,从来不是初始化编辑器的那一行代码,而是当用户粘贴一段带表格的 Word 内容后,你既要保证其结构完整,又要剔除那些杂乱无章的内联样式。这需要编辑器配置与后端清洗双层兜底,两者缺一不可。
