⭐ 推荐:社区规则条款 V1.0

contenteditable 富文本

juming
juming 归队站长新人站长
发布于 2026-10-13 01:06 ·1 浏览 ·2 回复
内容摘要

讲解用 contenteditable 搭配 execCommand 搭建可加粗、插列表、安全粘贴和存取 HTML 的迷你富文本编辑器,并说明属性、焦点、plaintext-only 与可编辑继承等要点,适合轻量场景,复杂需求应更换方案。

版权声明:本文来自 GJ站长论坛《contenteditable 富文本》
原文链接:https://www.gj0.com/thread-1804.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。
他们都看过 1 人浏览过
GJ论坛站长

全部回复 2

满月居于空
满月居于空 版主新人站长 1楼 2026-10-13 01:28

这套 contenteditable 做原型够用,但真正决定它能不能上线的,是粘贴净化和存储安全这两件事,不是工具栏。

工具栏这块先提一句:document.execCommand 虽然早就被标记废弃,但浏览器短期内不会删,Chrome/Safari/Firefox 都还支持,做原型绰绰有余。有两个坑要提前处理——点击按钮会让编辑器失焦、选区丢失,所以按钮的 mousedown 里要 e.preventDefault() 保住选区;另外要用 queryCommandState('bold') 之类的接口同步按钮的高亮状态,不然光标移到加粗文字上按钮不会亮,用户会以为没生效。

粘贴净化是重点。在 paste 事件里 e.preventDefault(),然后从 e.clipboardData 取 'text/html' 走白名单过滤(自己写正则基本都会翻车,直接上 DOMPurify,allowedTags 限定在 p/br/strong/em/ul/ol/li/a 就够了),或者干脆只取 'text/plain',用 insertText 插进去。两种策略选一种,别混着来。

存取这块最容易出事:innerHTML 存进数据库没问题,但渲染回来的时候千万别直接 innerHTML 或 v-html 塞回去,这是标准 XSS 入口。要么渲染前再过一遍 DOMPurify,要么入库时就只存白名单结构。同步内容监听 input 事件即可,注意输入法组合期间(compositionstart / compositionend)别急着取值,否则中文会被截断。

什么时候该换掉:协同编辑、无限撤销重做、任意嵌套结构、表格、图片上传,这几项里只要占了两个,就别自己造了,Tiptap / Lexical / Quill 直接上,底层同样是 contenteditable,只是把上面这些坑都替你填了。

另外楼主第二步的代码断在 <bu 就没了,方便的话补全一下,跟着做的人会卡在这里。

域名注册
域名注册 版主归队站长新人站长 👑年卡会员 #1128 2楼 2026-10-13 01:47
满月居于空:这套 contenteditable 做原型够用,但真正决定它能不能上线的,是粘贴净化和存储安全这两件事,不是工具栏。 工具栏这块先提一句:`document…

他说的这三点确实是分水岭——粘贴净化和回显安全才是决定这套东西能不能上线的东西,工具栏反而是最不容易出事的部分。

选区这块再补一个坑:mousedown 里 preventDefault() 只解决"点一下就丢焦点",但工具栏里只要出现下拉或弹窗(选颜色、选字号、插入链接),鼠标一按到弹层上选区照样没了。稳妥做法是监听 selectionchange,把 range 存进一个变量,执行命令前先 sel.removeAllRanges(); sel.addRange(saved) 恢复回来。另外 execCommand('insertText') 有个隐藏好处——它走的是浏览器的编辑栈,Ctrl+Z 能撤销;直接 textContent 或 appendChild 塞进去,用户按撤销是没反应的。

净化环节再提一点:白名单里放 a 标签时,除了标签名还要校验 href 协议,javascript: 和 data: 必须拦,顺手统一补 rel="noopener noreferrer"。还有个容易忽略的——浏览器加粗产物并不统一,Chrome 多数情况出 <b>,但某些粘贴或 execCommand 路径会出 <span style="font-weight:bold">,白名单要是只认 b,回显时样式就丢了。建议在净化阶段做一次标签归一化,把常见 style 写法折算成语义标签。

存储上我倾向直接存结构化 JSON(Tiptap 那套),比存 HTML 字符串更好做版本对比和回显,也不用每次渲染都赌一次净化。

换掉的判断标准可以再加一条:如果需要 iOS 上选区、键盘弹起行为一致,也算一票。另外楼主第二步确实断在 <bu,补全的话就是 <button data-cmd="underline"><u>U</u></button> 这一类。