动态插入的 input 无法 focus 是因浏览器拒绝聚焦未就绪元素,需满足四个条件:元素已渲染且可见、在文档流中、调用由用户手势触发、未被禁用或设 tabindex="-1";推荐用 requestanimationframe 聚焦并检查 activeelement 和 offsetparent。

动态插入的 input 为什么 focus() 没反应
不是 JS 写错了,而是浏览器拒绝聚焦一个“还没准备好”的元素。DOM 插入完成 ≠ 渲染完成 ≠ 焦点系统就绪。常见于 innerHTML、insertAdjacentHTML、Vue 的 v-if 切换、React 的 useState 触发重渲染后。
实操建议:
- 别用
setTimeout(() => input.focus(), 0)—— 它不保证渲染完成,比requestAnimationFrame更不可靠 - 统一用
requestAnimationFrame(() => input.focus()),它在下一次绘制前执行,能等 DOM 布局就绪 - 若元素在 Shadow DOM 中,先确认
shadowRoot已挂载,再查子节点并聚焦 - 检查
input是否被display: none或visibility: hidden包裹:getComputedStyle(input).display !== 'none'必须为真
focus() 调用前必须检查的四个硬性条件
浏览器对 focus() 静默失败,不报错也不提示。你只能靠排查。漏掉任一条件,光写 input.focus() 就是白调。
实操建议:
-
document.activeElement !== input:先判断是否已是焦点,避免无效调用 -
input.offsetParent !== null:确保元素在文档流中(没被移除、没被display: none掩盖) - 调用上下文必须是用户手势触发(如
click、keydown回调),否则 iOS Safari 和部分 WebView 会拦截 - 元素不能被
disabled,也不能是tabindex="-1"(它只支持程序聚焦,不参与 Tab 流;需可导航请用tabindex="0")
模态框打开后焦点没进内容区
这不是“没聚焦”,而是焦点落在了模态框容器本身(比如 <dialog></dialog> 或 <div role="dialog">),而它的子元素又没被正确识别为可聚焦项。
<p>实操建议:</p>
<ul>
<li>不要依赖 <code>autofocus 属性——它只在 HTML 初始解析时生效,对动态插入的元素完全无效
<dialog></dialog> 用 shown 事件;Bootstrap 用 shown.bs.modal;React 用 useEffect 配合 ref;Vue 用 onMounted + nextTick
modal.querySelector('button, input, select, textarea, [tabindex="0"]'),避开 [tabindex="-1"]
{ preventScroll: true } 参数防止软键盘弹出时页面滚动:input.focus({ preventScroll: true })(注意 Safari 15.4+ 才支持)Tab 键跳出模态框或滚动容器
焦点“消失”不是丢了,是容器本身没资格进 Tab 流。比如 <div class="card-list"> 滚动区域没设 <code>tabindex="0",Tab 到末尾就跳到页面底部;模态框没做焦点围栏,Tab 就直接穿出去。
实操建议:
- 滚动容器必须设
tabindex="0",否则无法自动滚动到新焦点项 - 监听模态框的
focusin事件(它冒泡,focus不冒泡),检查!modal.contains(event.target),不满足就立刻firstFocusable.focus() - Tab 循环不能只靠
keydown拦截所有Tab—— 屏幕阅读器快捷键不会触发 keydown,但会改变焦点。必须配合focusin捕获所有入口 - 每次内容更新(如 AJAX 加载、框架 re-render)后,必须重新运行
modal.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])'),旧列表会过期
body 上导致下一次 Tab 断链——这些都不是单次 focus() 能解决的,得靠状态锁、容器级事件委托和可聚焦元素实时快照。











