输入处理:拦截粘贴,把富文本洗成纯文本

目标

用户从网页/Word 粘贴内容时,会带进一堆杂乱样式和奇怪标签。本节课拦截 paste 事件,取出纯文本,用 Range 手动插入,杜绝脏 HTML。

运行环境:浏览器。运行方式:在 l1 的 index.html 里,把 <script> 换成下面内容,从别处复制一段带格式文字粘贴进编辑器,会发现只进来纯文本。

<script>
  const editor = document.getElementById('editor')
  editor.addEventListener('paste', (e) => {
    e.preventDefault() // 阻止浏览器默认粘贴(默认会带格式)
    const text = (e.clipboardData || window.clipboardData).getData('text/plain')
    const sel = window.getSelection()
    if (!sel.rangeCount) return
    const range = sel.getRangeAt(0)
    range.deleteContents()              // 删掉选中的(若有)
    range.insertNode(document.createTextNode(text)) // 插入纯文本节点
    range.setStartAfter(range.lastChild) // 把光标移到文本末尾
    range.collapse(true)
    sel.removeAllRanges()
    sel.addRange(range)
  })
</script>

clipboardData.getData('text/plain') 只拿纯文本;preventDefault 让默认的"带格式粘贴"不发生,脏 HTML 根本进不来。

名词解释

paste 事件:粘贴时触发,事件对象带 clipboardData(剪贴板数据)。preventDefault() 可阻止默认粘贴行为。
clipboardData.getData(type):按 MIME 类型取剪贴板内容,'text/plain' 是纯文本,'text/html' 是带格式 HTML。
受控输入(Controlled Input):不让浏览器自由改 DOM,而是拦截每一次输入、由自己的逻辑决定最终写入什么——这是可控编辑器的核心。

课后练习

练习:改进粘贴处理,让纯文本里的换行 \n 也保留为编辑器里的换行(用 <br> 或 \n 均可,本例用 \n 由 contentEditable 自行处理)。

答案:上面的实现已经逐字插入纯文本,其中包含的 \n 在 contentEditable 中通常会被保留。若想显式转成 <br>,可把 text 按 \n 切分、用 <br> 连接后插入:

const parts = text.split('\n')
const frag = document.createDocumentFragment()
parts.forEach((line, idx) => {
  if (idx) frag.appendChild(document.createElement('br'))
  frag.appendChild(document.createTextNode(line))
})
range.insertNode(frag)

总结

粘贴是富文本编辑器"脏数据"的最大入口。Word/网页复制来的 HTML 充满 style、font、span 和无效标签,直接放进来会污染你的数据模型、还可能带来 XSS。拦截 paste、只取 text/plain、自己决定怎么插入,是把"输入受控"落到实处的范本。这背后是同一句话:浏览器想自由改 DOM,你要接管每一次修改。顺着这个思路,beforeinput 事件能拦截得比 paste/input 更早(在浏览器落 DOM 之前),是做"受控编辑器"的更彻底方案——下一节处理块级格式时就会用到这类思路。