唯一可靠方式是子页主动上报焦点状态:子页监听 focusin/focusout 并校验目标是否为可交互元素,通过 postMessage 通知父页;父页结合 IntersectionObserver 判断可视性,且需处理 Shadow DOM 定位、跨域、移动端兼容及懒加载时机。

监听 iframe 的 focus 事件不靠谱
直接给 iframe 绑定 focus 或 blur 事件基本无效——iframe 元素自身不会获得焦点,除非它设置了 tabindex(但即便如此,焦点也只落在 iframe 容器上,而非其内部文档)。你看到的“用户在操作 iframe 里输入框”,实际是子页面 DOM 获得了焦点,父页完全收不到通知。
用 postMessage 主动上报焦点状态
唯一可靠的方式是子页主动上报。子页需在 window.addEventListener('focusin', ...) 和 window.addEventListener('focusout', ...) 中判断目标是否为可交互元素(如 input、textarea、contenteditable),再通过 postMessage 通知父页。
- 子页必须校验
event.target是否属于表单控件或可编辑区域,避免误报div点击 - 父页收到消息后,应记录当前活跃的
iframe引用(如用iframe.dataset.id标识),而不是仅存 origin - 不要依赖
document.activeElement在父页中反查——跨域时返回null,同源时也可能因 Shadow DOM 封装失效 - 移动端需额外监听
touchstart,因为部分安卓 WebView 不触发focusin事件
配合 IntersectionObserver 判断“可视+可交互”状态
仅知道哪个 iframe “有焦点”还不够,用户可能正滚动到另一个 iframe 区域但尚未点击。这时要叠加视口可见性判断:
- 用
IntersectionObserver监听每个 iframe 的isIntersecting状态 - 当某个 iframe 同时满足
isIntersecting === true且收到子页的focusin消息,才认为“用户正在与之交互” -
rootMargin建议设为"0px 0px 200px 0px",提前捕获即将进入视口的 iframe,避免滚动停顿后才响应 - 若 iframe 是懒加载的(
data-src+ 动态赋值src),必须等load事件完成后再启动IntersectionObserver,否则 observer 可能初始化失败
Shadow DOM 内 iframe 的焦点穿透难点
如果 iframe 挂在 shadowRoot 里,子页发来的 focusin 消息仍能被父页收到(postMessage 不受 Shadow DOM 封装影响),但父页无法用 document.querySelector 直接定位到该 iframe 元素——必须先从 shadow host 开始查起。
立即学习“前端免费学习笔记(深入)”;
- 不能写
document.querySelector('iframe[data-id="chat"]'),而要写shadowHost.shadowRoot.querySelector('iframe[data-id="chat"]') - 若存在多层 shadow host(比如自定义组件嵌套),需逐层
.shadowRoot访问,不能靠querySelector跨越封装边界 - Playwright 或 Puppeteer 测试时,必须用两段式 locator:
page.locator('my-widget').locator('iframe'),不能合并成单个选择器 - 别试图在父页监听子 iframe 的
focus事件委托——Shadow DOM 的事件流不会冒泡到 light DOM



















