浮层内的焦点需被限制在可交互范围
别名: 焦点陷阱 · inert · focus containment · 焦点环 · modal focus
概念解释
模态对话框、抽屉、全屏菜单把画面占住之后,键盘焦点仍走文档里那条全局 Tab 链——链并不会因为图层在最上面就自动停在浮层边。焦点限制(focus trap / focus containment)把这条链在浮层的可交互范围内剪断并接成环:Tab 与 Shift+Tab 只在浮层里的控件之间循环,背后页面不可聚焦也不可点。限制的对象是「现在还能操作的那些节点」,不是浮层 DOM 里的每一个盒子。
它处理的是浮层打开期间焦点往哪走,不是打开时焦点该不该移入,也不是关闭后焦点该不该还回去。
机制
顺序焦点导航按可聚焦节点的文档顺序走一条开链。浮层只是更高的绘制层,不改这条链,所以最后一个浮层按钮之后的下一个停靠点往往是背后页面上的链接。用户看不见背后,却能把焦点送出去,输入落在被挡住的控件上,读屏也会开始读「下面那页」。
剪断链条需要两边同时做。向前向后都在浮层首尾控件之间折返,否则 Shift+Tab 仍会漏出。背后子树加上 inert(或等效地去掉焦点并拦截指针),既让 Tab 扫不到,也让点击、点按、无障碍树探索到不了被挡住的页面。装饰性遮罩、不可聚焦的说明文字不必进环;环里漏掉任何一个能点的控件(包括关闭),键盘用户就无法完成浮层内的任务。pointer-events: none 只挡指针,不挡 Tab。
边界
非模态的气泡、悬停菜单、页面内的筛选板不应做焦点限制——用户还要和周围内容交替操作,把焦点锁死等于绑住上下文。嵌套浮层只锁最上面那一层,底层浮层的环要让路,不能两个陷阱抢焦点。单页里同时出现的非模态弹出若被误加上 inert 会把主页面一起废掉。触摸为主、没有外接键盘的场景仍然需要限制:外接键盘和无障碍开关随时会接上。没有可聚焦控件的纯告知层,至少要把关闭或确认做成环内唯一节点,不能让焦点掉到 body。
怎么落地
- 模态打开期间,把 Tab 循环限制在浮层内全部可交互控件(含关闭),并对背后根节点设
inert或同等的不可聚焦、不可点。 - 不要只用半透明遮罩或
pointer-events;不要把不可聚焦的装饰节点强行放进循环。 - 嵌套对话框只保留最上层的陷阱,下层陷阱暂停。
- 验证:打开浮层后连续 Tab 与 Shift+Tab 至少两圈,焦点不得出现在浮层外;点击背后应无响应。接上屏幕阅读器,确认探索范围停在浮层,而不是读到被挡住的标题。