input事件无法捕获粘贴富文本、execcommand格式操作、拖拽图片等行为,需组合监听input+paste+blur+compositionend;读取时禁用innerhtml/innertext,改用textcontent校验+dompurify净化;焦点控制需显式设置tabindex并用range定位光标;服务端必须二次校验标签与属性。

为什么 input 事件监听不到所有编辑行为
加了 contenteditable="true" 后监听 input 事件,看似能捕获按键、删除、回车,但实际漏掉大量变更:粘贴富文本后 DOM 结构突变、用 document.execCommand(哪怕已废弃)加粗/列表、拖拽插入图片、甚至 iOS 软键盘收起前的最后一次输入。这些操作在部分浏览器中不触发 input,或只在 focus 失去后才补发一次。
真正稳定的做法是组合监听:input + paste + blur(作为兜底),必要时补充 compositionend(中文输入法上屏)。
-
input:覆盖绝大多数实时输入场景,但对格式操作无感 -
paste:必须event.preventDefault(),再手动取纯文本或净化 HTML,否则直接插入的 Word 样式会污染结构 -
blur:不是主力,但能 catch 到快速切 Tab 或点击保存按钮导致的未触发情况
如何从 contenteditable 元素安全读取当前状态
不能直接读 element.innerHTML 就往模型里塞——它可能含 <div><br></div>、<p><br></p>、内联 style、data- 属性,甚至被用户粘贴进来的 <img onerror="alert(1)">。也不该用 element.innerText,它会把换行符转成空格,丢失段落结构。
推荐分层处理:
- 模型层存语义化结构:用
element.textContent做基础校验,再结合DOMPurify.sanitize(element.innerHTML, { ALLOWED_TAGS: ['strong', 'em', 'ul', 'li', 'p', 'br'] })得到干净 HTML - 前端展示层用
innerHTML渲染,但必须加sanitized标记,避免二次渲染 XSS - 若需光标位置同步(比如协作编辑),必须用
getSelection()+Range实时抓取,不能靠 DOM diff
tabIndex 和 focus 状态不一致导致模型不同步
调用 element.focus() 后,光标常卡在开头或消失,尤其当内容为空或含嵌套 <p><br></p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill4293" title="Doc To HTML"><img
src="https://img.php.cn/upload/skill/000/000/081/178998486916110.jpg" alt="Doc To HTML" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill4293" title="Doc To HTML" class="overflowclass">Doc To HTML</a>
<p class="overflowclass">使用 MinerU 文档处理引擎将 Word 文档(.doc、.docx)转换为保留结构和格式的干净 HTML。</p>
</div>
<a rel="nofollow" href="/xiazai/skill4293" title="Doc To HTML" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div> 时。此时 input 事件仍会触发,但用户实际没在编辑——模型误更新,且无法还原光标。
必须显式控制焦点和光标:
- 初始聚焦前确保
element.tabIndex = "0",否则非表单元素无法获得键盘焦点 - 聚焦后立即用
const range = document.createRange(); range.selectNodeContents(element); range.collapse(false);把光标推到末尾 - 禁用右键菜单:
element.oncontextmenu = () => false,防止拼写检查浮层干扰选区判断
服务端接收时模型状态校验容易被忽略
前端用 DOMPurify 过滤了,不代表后端可以放松。攻击者可绕过前端直接发请求,传入 <svg onload="fetch('/steal')"></svg> 或带 javascript: 的链接。
后端必须做两件事:
- 剥离所有属性名匹配
/^on\w+$/i或值含javascript:、data:text/html、vbscript:的节点 - 白名单校验标签:只允许
p、br、strong、em、ul、ol、li,其余一律剔除
复杂点不在前端监听逻辑,而在 DOM 结构不可控性——同一段“换行”,Chrome 插 <div><br></div>,Firefox 插 <br>,Safari 可能插 <p><br></p>。模型同步必须接受这种差异,而不是试图统一 DOM 表现。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










