autofocus仅在HTML首次解析时对源码中第一个可聚焦元素生效,后续动态添加或条件渲染的autofocus均无效;须用focus()配合isConnected、offsetParent、tabIndex等四条件校验,并在用户手势中调用。

autofocus 属性在多个弹窗或条件渲染组件中必然失效,且无法靠“多写几个”解决焦点争抢——它只对 HTML 初始解析时第一个可聚焦元素生效,其余全部静默忽略。
为什么多个 autofocus 元素不会轮流聚焦
浏览器规范明确:整个文档中,autofocus 仅在 HTML 字符串首次解析阶段扫描一次,且只对**源码中第一个满足条件的可聚焦元素**触发 focus()。后续所有带 autofocus 的元素(哪怕 DOM 已挂载、已 visible)都不会被重新评估。
- 常见错误现象:
<dialog id="login"><input autofocus></dialog>和<dialog id="search"><input autofocus></dialog>同时存在 → 只有login里的 input 获得焦点,search的完全无反应 - 服务端渲染(SSR)+ 客户端 hydrate 场景下,若 SSR 输出里已有
autofocus,客户端动态插入的新弹窗即使带该属性也无效 -
v-if或{show && <Dialog />}渲染的弹窗,其内部autofocus在挂载时早已“过期”
用 focus() 替代 autofocus 时的关键校验点
手动调用 element.focus() 是唯一可靠路径,但必须同步检查四个硬性条件,否则静默失败:
-
element.isConnected === true(已挂载到 document) -
element.offsetParent !== null(未被display: none或visibility: hidden隐藏) -
element.tabIndex >= 0 && !element.disabled && !element.hasAttribute('inert')(可聚焦且未锁定) -
document.activeElement !== element(避免重复聚焦干扰用户当前操作)
推荐统一用 requestAnimationFrame(() => element.focus()) 延迟一帧,避开 CSS transition 未结束、DOM 渲染未完成等时机问题。
立即学习“前端免费学习笔记(深入)”;
iOS Safari 下 focus() 必须绑定用户手势
在 iOS Safari 中,focus() 若不在用户点击、触摸或键盘事件的同步上下文中调用,会直接被浏览器拒绝——光标不跳、键盘不弹,且不报错。
- 错误写法:
useEffect(() => ref.current?.focus(), [])(React)或onMounted(() => inputRef.value?.focus())(Vue)→ 大概率白忙活 - 正确写法:在打开弹窗的按钮
onClick回调内立即调用inputRef.current?.focus(),确保链路未脱离原始手势 - 路由跳转后需聚焦?监听
popstate或框架的afterEach钩子,并确认该导航由用户点击触发(如history.pushState是点击菜单产生的)
多弹窗共存时如何确保焦点落在最上层
焦点归属不是“谁先渲染”,而是“谁当前应获得焦点”。不能依赖渲染顺序或 z-index,而要靠显式状态控制:
- 给最上层弹窗加
data-modal-layer="top"属性,JS 只对[data-modal-layer="top"] [autofocus], [data-modal-layer="top"] input:not([disabled]):first-of-type类选择器匹配的元素调用focus() - 关闭弹窗时,显式执行
document.activeElement?.blur(),防止焦点残留导致 Tab 键跳转异常 - 监听
keydown时,用event.target.closest('[data-modal-layer]')检查输入是否发生在当前激活弹窗内,否则event.preventDefault()阻断
真正难的不是“怎么聚焦”,而是判断“该不该聚焦”——比如用户正在编辑旧弹窗,新弹窗弹出时若强行抢焦点,体验反而更差。时机、上下文、优先级,三者缺一不可。



















