粘贴富文本时自动转纯文本的原理是拦截paste事件,阻止默认行为,读取clipboardData中的text/plain内容并手动插入;需处理兼容性、权限及多格式剪贴板数据问题。

粘贴富文本时自动转纯文本的原理
浏览器默认会把 Word、微信、网页复制的内容连同样式、HTML 标签一起带进来,contenteditable 元素或 textarea 粘贴后出现乱码、隐藏标签、多余换行,本质是剪贴板里存了多个格式(如 text/html 和 text/plain),而编辑器优先用了 HTML 版本。
真正可控的做法不是等粘贴完再“清洗”,而是从源头拦截——在 paste 事件里阻止默认行为,手动读取 clipboardData.getData('text/plain'),再插入纯文本。
常见错误现象:innerHTML 直接赋值导致 XSS;用正则删 <.*?> 漏掉注释或自闭合标签;调用 innerText 后丢失换行。
- 只监听
paste事件,别依赖input或change后处理 - 必须调用
event.preventDefault(),否则浏览器仍会按默认逻辑插入富文本 - 部分旧版 Safari 不支持
clipboardData.getData('text/plain'),需降级 fallback 到clipboardData.getData('text/html')再用 DOMParser 清洗
用 execCommand('insertText') 的兼容性陷阱
很多老项目用 document.execCommand('insertText', false, plainText) 实现粘贴拦截,但它在 Chrome 90+ 已被废弃,Firefox 也标记为不推荐,Safari 完全不支持。更麻烦的是:它会无视光标位置,在内容末尾强行追加。
立即学习“前端免费学习笔记(深入)”;
实际场景中,用户在中间位置粘贴,结果文字跑到段落最后,体验直接崩坏。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 现代替代方案是
getSelection().getRangeAt(0).insertNode(textNode),但要注意 IE11 不支持insertNode,得用range.deleteContents()+range.insertNode() - 如果目标元素是
textarea,不能用 execCommand,得改用setSelectionRange+ 字符串拼接 -
execCommand在非contenteditable元素上无效,别在div上瞎试
ClipboardEvent.clipboardData 读取失败的几种原因
有时 event.clipboardData.getData('text/plain') 返回空字符串,不是代码写错了,而是权限或上下文问题。
典型触发条件:页面没焦点、粘贴发生在 iframe 里、Chrome 扩展劫持了剪贴板、或者用户复制的是图片/文件(此时 text/plain 为空)。
- 务必先检查
event.clipboardData.types是否包含'text/plain',再读取,避免报错 - 移动端 Safari 对
clipboardData支持极弱,iOS 16.4 前几乎不可用,得用document.execCommand('paste')配合权限弹窗(但用户反感) - 若读取失败,不要静默 fallback 到原生粘贴,至少给个提示:“检测到富文本,已自动清理”
CSS 层面的“视觉纯文本”误导
有些团队以为加个 user-select: text; white-space: pre-wrap; 就算纯文本了,其实只是看起来没样式——粘贴进来的 <span style="font-weight:bold"> 依然存在,后续导出 PDF 或同步到后端时照样暴露。
真正的“纯文本”必须剥离所有节点结构,只留文本节点和规范换行符(\n,不是 <br> 或 <p>)。
- 别信
innerText,它会把<img alt="abc">变成 "abc",但用户没复制 alt 文本,这是信息污染 - 用
textContent更安全,但它保留所有空白符,需要额外.replace(/\s+/g, ' ').trim() - 如果后端要校验输入,前端清洗只是第一道防线,服务端仍需做白名单过滤,不能省
最易被忽略的一点:用户长按手机选中一段文字再粘贴,iOS 会生成带 text/rtf 格式的剪贴板数据,而绝大多数 JS 清洗逻辑完全不处理这个类型,直接漏过。


















