contenteditable="plaintext-only" 不能单独依赖,因 Firefox 不支持而退化为富文本编辑,需结合 JS 拦截 paste/input、MutationObserver 清洗及光标位置管理才能可靠实现纯文本限制。

直接用 contenteditable="plaintext-only" 是最简方案,但浏览器支持不均,实际项目中必须搭配 JS 拦截和 DOM 清洗才能可靠生效。
为什么 contenteditable="plaintext-only" 不能单独依赖
这个值在 Chrome 120+、Edge 120+ 和 Safari 17.4+ 中已稳定支持,但 Firefox 至今(2026 年 8 月)仍完全不识别——它会退化为 contenteditable="true",用户粘贴 Word 或网页内容时照样塞进 <span style="font-weight:bold"> 这类标签。
实操建议:
- 先写
contenteditable="plaintext-only"作为声明意图,同时加-webkit-user-modify: read-write-plaintext-only兼容旧版 Safari/iOS - 对 Firefox 和部分 Android WebView,必须靠 JS 补位:监听
paste和input,强制走纯文本路径 - 别指望它阻止拖拽插入 HTML 片段——这类操作绕过 paste 事件,只能靠
MutationObserver后置清洗
拦截 paste 事件取纯文本的正确写法
用户 Ctrl+V 粘贴时,event.clipboardData.getData('text/plain') 是唯一可控入口。但注意:部分浏览器(如旧版 Edge)可能返回空字符串,需 fallback 到 'text/html' 再用 DOMParser 提取文本。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- 始终调用
event.preventDefault(),否则浏览器会按默认逻辑插入富文本 - 用
document.execCommand('insertText', false, text)插入(虽已废弃但目前最稳),避免直接设innerHTML引发 XSS - 若用
insertAdjacentText,需手动管理光标位置:先getSelection().getRangeAt(0)记录位置,插入后再range.insertNode()
input 事件无法覆盖全部输入场景
input 对键盘打字响应及时,但对中文 IME 上屏、撤销重做(Ctrl+Z)、甚至某些拼音输入法的“逗号上屏延迟”都不可靠。更隐蔽的是:用户右键菜单里的“粘贴”选项触发的 paste,有时不冒泡到绑定元素上。
实操建议:
- 组合监听:
input+compositionend(捕获中文输入完成)+blur(防失焦未保存) - 对关键容器加
MutationObserver,观察childList变化,一有新节点就立即textContent替换或白名单过滤 - 清洗时别用
innerHTML = innerHTML.replace(/]+>/g, '')——它会把<script>当普通标签删掉,但无法防onerror=alert(1)这类属性型 XSS
光标位置丢失是纯文本限制下的高频副作用
每次用 JS 清洗 DOM(比如删掉 span 标签),浏览器都会重排光标,常跳到开头或末尾。尤其在 Vue/React 动态渲染后,contenteditable 容器的 innerHTML 被重写,selection 几乎必然失效。
实操建议:
- 清洗前用
getSelection().getRangeAt(0).cloneRange()保存位置,清洗后用range.collapse(true)+range.insertNode()恢复 - 避免在
input回调里同步清洗——改用queueMicrotask延迟到 DOM 更新后执行 - 如果只是要保留换行,别碰
innerText(它会把\n全转成空格),改用textContent+ 正则替换/\r\n|\r|\n/g为<br>,再插入
真正可靠的纯文本限制,从来不是单点配置,而是 paste 拦截、input 监听、MutationObserver 清洗、光标快照四层叠加。其中任意一层漏掉,用户都能通过拖拽、右键粘贴、或开发者工具注入绕过。



















