直接赋值 innerHTML 会出问题,因为浏览器原样插入剪贴板中未过滤的 text/html,可能含 script/style 标签、内联样式、零宽字符或 XSS 风险;应使用 DOMParser + 白名单清洗后插入。

粘贴时为什么直接赋值 innerHTML 会出问题
因为浏览器默认把剪贴板里的 text/html 原样插入,里面可能含 <style>、font、span 嵌套、不可见字符(如 \u200b)、甚至残留的 <script>。这不是 bug,是规范行为——它忠实地还原来源内容。
常见错误现象:el.innerHTML = clipboardData.getData('text/plain') 看似安全,但 macOS Notes、企业微信等应用写入的 text/plain 可能丢失换行、混入零宽空格,导致段落塌陷成一行;而直接用 innerHTML 接收未过滤的 HTML,则可能触发 XSS 或样式污染。
- 优先尝试
clipboardData.getData('text/html'),哪怕只是作为 DOM 解析的中间载体 - Safari 对
text/html返回空字符串,必须 fallback 到text/plain并加.replace(/\u200b/g, '') - 别用正则删标签:容易漏掉注释、CDATA、自闭合标签或嵌套结构
用 DOMParser + 白名单做安全清洗
比正则可靠,也比 innerText 保留结构更可控。核心是解析后只留语义明确、样式中立的标签,删掉所有 style 属性和 class。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 创建
DOMParser实例,用parseFromString(html, 'text/html')得到文档片段 - 递归遍历子节点,只保留
p、br、strong、em、ul、ol、li—— 其他一律移除 - 对保留节点调用
removeAttribute('style')和removeAttribute('class') - 最后用
fragment.body.innerHTML提取清洗后 HTML,或fragment.body.textContent提纯文本
监听 paste 事件的最小必要操作
只监听 input 或 blur 是不够的——粘贴动作在这些事件触发前已完成,用户看到的已经是污染后的 DOM。
必须在 paste 阶段拦截:
- 一定要调用
event.preventDefault(),否则浏览器仍会执行默认插入逻辑 - 不要依赖
document.execCommand('insertText'):Chrome 98+ 已失效,且无视光标位置,文字总插到末尾 - 现代做法是用
getSelection().getRangeAt(0)获取当前光标位置,再用range.insertNode()插入清洗后的节点 - IE11 不支持
insertNode,需 fallback 到range.deleteContents()+range.insertNode()组合
contenteditable="plaintext-only" 能不能替代手动清洗
不能。这个值目前仅 Safari 支持,Chrome/Firefox 忽略它,行为退化为普通 contenteditable="true"。即使在 Safari 中,它也只是禁用富文本格式(如加粗、列表),不解决粘贴带来的冗余标签、内联样式或 XSS 风险。
真正可控的做法仍是劫持 paste 事件并主动清洗——这是跨浏览器唯一稳定路径。
容易被忽略的一点:清洗后插入的内容若含 img 标签,其 src 可能是 blob URL 或 data URL,需确认是否允许上传或转存;否则用户刷新页面后图片就丢了。



















