最直接判断编辑器是否持有全局焦点的方式是检查 document.activeElement === editorEl;若焦点在内部子元素则用 editorEl.contains(document.activeElement) 判断,再排除 INPUT/TEXTAREA/SELECT 等非编辑意图元素,并监听 focusin 事件而非轮询。

document.activeElement 是否等于编辑器容器元素
判断编辑器是否持有全局焦点,最直接的方式是看 document.activeElement 是否严格等于你的编辑器根节点(比如一个 div[contenteditable="true"] 或富文本编辑器的 wrapper 元素)。
注意:不能只检查 tagName 或 class,必须用引用相等(===),因为多个可编辑区域可能同为 DIV 或都有 editor 类。
- 先确保你有唯一可标识的编辑器 DOM 节点,例如:
const editorEl = document.querySelector('#my-editor') - 然后实时比对:
document.activeElement === editorEl - 如果返回
true,说明焦点确实在该编辑器容器上;false则不一定没焦点——可能焦点落在其内部子元素(如嵌套的input、button)上,需进一步判断
处理 contenteditable 内部子元素抢走 activeElement
当编辑器内嵌了可聚焦子元素(比如工具栏按钮、内联图片上传控件、或用户插入的 input),document.activeElement 会指向那个子元素,而不是容器本身。此时直接比较会误判“编辑器失焦”。
解决办法是向上遍历父级,看焦点元素是否属于编辑器作用域:
- 用
editorEl.contains(document.activeElement)替代严格相等判断 - 这个方法兼容所有情况:焦点在容器自身、在
br、在文本节点、在子span或img上,只要没跳出容器就返回true - 注意:若编辑器用了 Shadow DOM(
mode: "open"),contains()不会穿透,得改用editorEl.shadowRoot?.activeElement并递归检查
避开 input/textarea 等原生表单控件的干扰
很多富文本编辑器允许插入原生表单控件(比如嵌入一个 input type="text")。这类元素一旦获得焦点,document.activeElement 就变成它,且 editorEl.contains() 仍为 true —— 这其实是合理状态,但业务逻辑可能需要区分:“是编辑文字”还是“在操作嵌入控件”。
这时要加一层语义判断:
- 先确认焦点在编辑器内:
editorEl.contains(document.activeElement) - 再排除非编辑意图的元素:
!['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName) && !document.activeElement.isContentEditable - 如果两个条件都满足,才能认为“用户正在编辑富文本内容”
监听 focusin 而非轮询 activeElement
不要在定时器里反复读 document.activeElement —— 效率低,还容易错过快速切换。
正确做法是监听 focusin 事件,在 document 级统一捕获焦点进入行为:
-
focusin会冒泡,且能捕获所有入口(Tab、鼠标点击、focus()调用、屏幕阅读器切换) - 在 handler 里立刻检查
event.target是否属于编辑器,比读activeElement更及时、更可靠 - 特别注意:不要用
focus事件,它不冒泡,无法在 document 上监听到子元素获得焦点
复杂点在于,activeElement 的值在 focusin 触发时**已经更新**,所以你可以放心用它做判断;但如果你依赖 event.target,要注意它可能不是最终焦点元素(比如点击了 label,实际聚焦的是关联的 input)。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











