tabindex="-1"本身不控制焦点,仅使元素可被JavaScript主动聚焦;真正实现焦点管理需配合focus()调用及四大运行时校验(已挂载、未隐藏、可聚焦、非当前激活),且iOS Safari要求必须在用户手势同步上下文中执行。

HTML全局属性本身不直接“控制”焦点,只提供基础能力;真正控制焦点靠的是 tabindex 搭配 JavaScript 的 focus(),且必须满足多个运行时条件,否则静默失败。
tabindex="-1" 是唯一能配合 focus() 主动聚焦的合法值
tabindex="-1" 不让元素进 Tab 流,但允许 JS 调用 focus()。这是弹窗、侧边栏、动态卡片等场景下把焦点“拉进来”的唯一可靠入口。
- 必须加在**首个可操作子元素**上(如第一个
<button>或<input>),不是 Modal 容器本身 - 原生可聚焦元素(
button、input)默认支持focus(),但加tabindex="-1"可规避 SSR/hydration 时的跨浏览器差异 - 自定义控件(如
<div role="button">)必须同时有tabindex="-1"和role="button",否则focus()无效且语义缺失 - 若目标元素尚未渲染完成(如异步加载字段)、被
display: none隐藏、或不在视口内,focus()会静默失败
focus() 失效的四大硬性校验点
手动调用 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 渲染未完成等时机问题。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
iOS Safari 下 focus() 必须绑定用户手势
在 iOS Safari 中,focus() 若不在用户点击、触摸或键盘事件的同步上下文中调用,会被浏览器直接拒绝——光标不跳、键盘不弹,且不报错。
- 错误写法:
useEffect(() => ref.current?.focus(), [])(React)或onMounted(() => inputRef.value?.focus())(Vue)→ 白忙活 - 正确写法:在打开弹窗的按钮
onClick回调内立即调用inputRef.current?.focus() - 路由跳转后需聚焦?监听
popstate或框架的afterEach钩子,并确认该导航由用户点击触发(如history.pushState是点击菜单产生的)
Tab 键循环不能靠 tabindex 实现,必须拦截 + 手动聚焦
浏览器不会自动把 Tab 键限制在弹出层内部。tabindex 只影响单个元素是否可聚焦、是否入流、相对顺序,它不提供“区域锁定”能力。
- 监听弹出层根元素的
focusin事件,检测!modalEl.contains(e.target) - 若焦点移出,立即获取所有有效可聚焦项:
modalEl.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])') - 按方向手动聚焦首/末项:
focusable[0].focus()或focusable[focusable.length - 1].focus() - 不要监听
keydown拦截 Tab —— 这会破坏屏幕阅读器(如 NVDA、VoiceOver)的内置导航逻辑
最易被忽略的是:关闭弹出层后必须将焦点回退到触发源,而不是任其落在 body 或消失。这个动作和 tabindex 无关,但依赖你在打开前就记录好触发元素的引用或 ID —— 动态列表中的按钮尤其容易因 DOM 重渲染而失效。


















