必须拦截 paste 事件并手动清洗剪贴板内容,优先解析 text/html 并用 domparser 白名单过滤,避免 execcommand,精准插入到光标位置,保留必要排版语义。

直接设 contenteditable="true" 后粘贴 Word、微信或网页内容,必然带冗余 HTML 标签、内联样式、零宽字符甚至 XSS 风险——这不是 bug,是浏览器对剪贴板数据的忠实还原。必须拦截 paste 事件并手动清洗,否则所有后续处理(存库、渲染、搜索)都会出问题。
为什么不能依赖 event.clipboardData.getData('text/plain') 直接插入
看似最简方案,但实际踩坑最多:Safari 和部分 Android 浏览器在复制自 Notes、企业微信时,text/plain 常为空或丢失换行;macOS 复制可能混入 \u200b(零宽空格)导致段落塌陷;某些富文本源(如 Outlook)根本不写 text/plain 格式,只写 text/html。
实操建议:
- 始终优先尝试
event.clipboardData.getData('text/html'),哪怕只是作为 DOM 解析的中间载体 - fallback 到
text/plain时,必须加.replace(/\u200b/g, '').replace(/\u00ad/g, '') - 别用
innerText提取——它会把<br>、<p></p>的换行语义全丢掉
用 DOMParser 安全解析并白名单过滤 HTML
正则删标签(/]*>/g)漏掉注释、CDATA、自闭合标签里的危险属性,且无法识别语义结构。DOMParser 是唯一能可靠还原剪贴板 HTML 结构并逐节点控制的方式。
实操建议:
- 创建
new DOMParser().parseFromString(html, 'text/html')得到文档片段 - 只保留语义明确、样式中立的标签:
p、br、strong、em、ul、ol、li、a(保留href,删style和class) - 对每个
p或div[display: block]节点后手动补\n,避免段落合并 - 最后用
fragment.body.innerHTML取清洗后 HTML,或fragment.body.textContent取纯文本
插入时必须尊重光标位置,别用 execCommand('insertText')
document.execCommand('insertText') 在 Chrome 97+、Firefox 84+ 已废弃,Safari 完全不支持;它无视当前光标位置,总在内容末尾追加,用户在段中粘贴,文字却跑到结尾——这是最伤体验的兼容性陷阱。
实操建议:
- 用
getSelection().getRangeAt(0)获取当前选区范围 - 调用
range.deleteContents()清空选区(兼容 IE11) - 用
range.insertNode(document.createTextNode(cleanText))插入清洗后文本 - 目标是
textarea?改用setRangeText(),它支持精确光标控制,IE10+ 全兼容
缩进和换行保结构比去样式更难
很多清洗逻辑把全角空格、Tab、首行缩进当“冗余”一并删掉,结果段落视觉塌陷。但用户确实需要这些排版信息——尤其合同、公文、代码说明等场景。
实操关键点:
- 别用
.replace(/\s+/g, ' ')粗暴压缩空格,会吃掉缩进;应区分\t、、连续空格的语义 - 对
<p></p>内文本,先textContent.replace(/^[ \t\u3000]+/gm, '')去首行缩进(若不需要),再统一处理内部空格 - iOS 上粘贴后空白?大概率是残留
<span style="white-space: pre-wrap"></span>,清洗时需递归移除所有style属性
真正难的不是删标签,而是判断哪些换行该留、哪些空格该保、哪些语义该还原——这得结合业务场景定白名单,而不是套个正则就完事。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











