焦点丢失是因dom状态、渲染时机、用户手势上下文、浏览器策略四者未对齐,导致focus()静默失败;需校验元素可见性、同步调用、正确tabindex及挂载状态,并用requestanimationframe确保渲染完成后再聚焦。

焦点丢失不是代码写错了,而是可访问性链路断在了某个环节——DOM状态、渲染时机、用户手势上下文、浏览器策略,四者只要一个没对齐,focus() 就会静默失败。
focus() 调用后没反应?先查这四个硬条件
浏览器对 focus() 的执行非常严格,不满足任一前提都会直接忽略,且不报错:
-
document.activeElement已是目标元素 → 白调,应加判断:if (input && document.activeElement !== input) input.focus() - 元素被
display: none或visibility: hidden包裹 → 用getComputedStyle(input).display !== 'none'校验 - 调用不在用户手势同步上下文中 → iOS Safari 和部分 WebView 会拦截(如 AJAX 回调、
setTimeout、Promise.then中调用) - 元素未挂载进 DOM,或
tabindex设置错误 → 原生input默认可聚焦;div模拟输入框必须设tabindex="0",不能是-1
动态插入内容后焦点不自动落进去?autofocus 是摆设
autofocus 只在 HTML 初始解析阶段生效,JS 动态插入的 <input autofocus> 完全无效。常见于弹窗、模态框、v-if 或 useState 控制显隐的场景。
- 删掉所有动态内容里的
autofocus属性,它只会干扰调试 - 聚焦必须手动触发,且要等 DOM 真实挂载、样式计算完成、元素可见 ——
requestAnimationFrame(() => input.focus())比setTimeout(() => , 0)更可靠 - 若内容含异步加载(如 fetch 后渲染表单),需在数据插入并完成渲染后再聚焦,不能仅靠“DOM 插入完成”就调用
- React 中用
useEffect(() => { ref.current?.focus(); }, [isOpen]);Vue 中在onMounted+v-if为真后调用,不能放onBeforeMount
Tab 键跳出弹窗或菜单?焦点围栏不是拦 Tab,而是闭环跳转
原生 <dialog></dialog> 或 showModal() 在 Safari 和旧 Edge 中并不真正锁住焦点。只监听 keydown 并 preventDefault() 所有 Tab,会破坏屏幕阅读器导航(如 NVDA 的 Insert+F7)。
- 预缓存可聚焦元素:
const focusables = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])') - 在弹窗容器上监听
keydown,仅当event.key === 'Tab'且当前焦点在首项/末项时才拦截 - 拦截后手动跳转:
Shift+Tab→focusables[focusables.length - 1].focus(),Tab→focusables[0].focus() - 更关键的是监听
focusin:若!modal.contains(event.target),立即focusables[0].focus()—— 这能捕获鼠标点击、屏幕阅读器跳转等非键盘方式逃逸
移动端软键盘不弹出?focus() 必须在原始手势同步栈里
iOS Safari 对软键盘触发极其保守。哪怕你在按钮 onclick 里调用 showModal() 再 focus(),只要中间穿插了任何异步逻辑(包括 requestAnimationFrame),键盘大概率不弹。
- 最可靠写法:
button.addEventListener('click', () => { dialog.showModal(); input.focus(); })—— 两行都在点击事件的同步执行栈内 - React 中:
onClick={() => { setShow(true); inputRef.current?.focus(); }},前提是inputRef.current此刻已存在且可见(不能是条件渲染中尚未挂载的节点) - 需要滚动对齐时,优先用
input.focus({ preventScroll: true })(Safari 15.4+、Chrome 86+ 支持);旧版 fallback 为input.scrollIntoView({ block: 'nearest' }) - 输完一个字符就失焦?检查是否在
oninput或onChange中触发了组件重渲染,导致input被销毁重建
真正难的不是写对某一行 focus(),而是确保从用户点下鼠标那一刻起,DOM 状态、渲染帧、焦点位置、可访问性属性(aria-expanded、inert、tabindex)全部原子性同步更新 —— 少一步,键盘用户就会卡在看不见的地方。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











