直接监听 class/style 在编辑器中失效,因其修改走 dom property 路径而非 attribute;可靠方案是监听 data-* 属性并主动拦截关键 api 入口。

能监控,但必须绕过编辑器内部 DOM 操作的“反射干扰”——contenteditable 元素的 class、style、spellcheck 等属性被框架或用户快捷键修改时,往往走的是 DOM property 路径,MutationObserver 默认收不到;真正可靠的是监听 data- 属性 + 手动接管关键操作入口。
为什么直接监听 class/style 在编辑器里大概率失效
HTML 编辑器(如 div[contenteditable])频繁调用 el.className = '...' 或 el.style.cssText = '...',这些属于 DOM property 赋值,不触发 attributes: true 监听。即使配了 attributeFilter: ['class', 'style'],回调也几乎为零。
-
classList.add()、el.spellcheck = false、el.contentEditable = 'false'全部不触发 attribute mutation - 只有
el.setAttribute('class', ...)或el.removeAttribute('spellcheck')才会进回调 - 现代编辑器(如 Slate、TipTap)内部基本不用
setAttribute改样式,所以监听原生属性等于摆设
真正有效的监听策略:用 data-* 锚定 + 主动拦截入口
放弃对 class 和 style 的被动监听,转而用 data- 属性作为“安全开关”的唯一信道,并在编辑器初始化时主动劫持关键 API。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 给编辑器根节点加稳定锚点:
<div id="editor" contenteditable data-editor-state="ready"> <li>配置 <code>MutationObserver只监听attributes: true+attributeFilter: ['data-editor-state', 'data-locked', 'data-read-only'] - 在编辑器加载后,重写其暴露的
setReadOnly()、enableSpellcheck()等方法,在内部统一走setAttribute('data-locked', 'true') - 禁止第三方脚本直接改
contentEditable属性:用Object.defineProperty(el, 'contentEditable', { set() { /* 拦截 */ } })补漏 - 检查
mutation.target === editorEl,排除子节点误触 - 只响应
mutation.type === 'attributes'且mutation.attributeName在白名单内(如'data-locked') - 比对新旧值:
if (mutation.oldValue === 'false' && mutation.newValue === 'true')才算“锁死”动作 - 加来源校验:若调用栈含
chrome-extension://或zopim.com,立即标记为高危 - 第一行必须是
observer.disconnect(),断开监听再修 - 仅恢复目标属性:
editorEl.setAttribute('data-locked', 'false'),绝不用innerHTML或replaceWith() - 修完立刻
observer.observe(editorEl, config)恢复监听 - 高频区域(如客服插件常反复锁编辑器)加时间节流:
if (Date.now() - lastFixTime
回调中如何判定“非法篡改”而非用户正常操作
编辑器自身也会改 data- 属性,不能一改就报警。需结合上下文快速过滤:
修复时最易踩的坑:别让 observer 自己把自己干掉
一旦确认是非法锁定,恢复操作本身会再次触发 MutationObserver 回调,形成死循环。必须原子化处理:
真正难的不是监听到变化,而是区分“编辑器自己在切模式”和“插件在偷偷锁住用户输入”——前者有明确调用链和业务语义,后者往往无痕、无日志、且发生在 setTimeout 或 Promise.then 微任务里。得靠 data- 属性 + 主动 API 接管 + 调用栈分析三者咬合,才压得住。










