处理模态框中的焦点与键盘陷阱
使用 useRef 和事件监听器,让焦点保持在打开的模态框内,以满足无障碍要求。
处理模态框中的焦点与键盘陷阱 是 CoddyKit 上的免费 React Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。
欢迎
在本课中,您将实现模态框内部的焦点陷阱,使键盘用户无法通过按 Tab 离开打开的模态框,这是重要的可访问性要求(WCAG 2.4.3)。
为何需要焦点陷阱
模态框打开后,键盘用户应该只能与模态框中的内容交互。如果没有焦点陷阱,按 Tab 会将焦点移到模态框覆盖层后面的元素上,这些元素虽然在视觉上不可见,但仍然可以访问。
什么是焦点陷阱
焦点陷阱会拦截 Tab 和 Shift+Tab 键事件;当 focus 即将离开模态框时,它会将 focus 循环回模态框内的第一个或最后一个 focusable 元素。
查找可获得焦点的元素
请使用 querySelectorAll 和一个涵盖所有交互元素的选择器。过滤掉已禁用和隐藏的元素。
const FOCUSABLE = [
'a[href]', 'button:not([disabled])',
'input:not([disabled])', 'select', 'textarea',
'[tabindex]:not([tabindex="-1"])'
].join(',');
const focusable = Array.from(
modalRef.current.querySelectorAll(FOCUSABLE)
);实现焦点陷阱
监听模态框容器上的 keydown 事件。按 Tab 时,如果 focus 位于最后一个元素上,就将其移到第一个元素。按 Shift+Tab 时,如果 focus 位于第一个元素上,就将其移到最后一个元素。
function handleTab(e) {
const focusable = /* array of focusable elements */;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault(); last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault(); first.focus();
}
}
}打开时移动焦点
模态框打开时,将 focus 移到其中第一个 focusable 元素上(或者使用 `tabIndex=-1` 将 focus 移到模态框容器本身)。这样可以防止 focus 停留在打开模态框的按钮上。
useEffect(() => {
if (isOpen) {
const first = modalRef.current?.querySelector(FOCUSABLE);
first?.focus();
}
}, [isOpen]);关闭时恢复焦点
模态框关闭时,将 focus 返回到模态框打开前处于 active 状态的元素。在模态框打开时,将 `document.activeElement` 保存到一个引用中。
const previousFocus = useRef(null);
useEffect(() => {
if (isOpen) {
previousFocus.current = document.activeElement;
} else {
previousFocus.current?.focus();
}
}, [isOpen]);使用 focus-trap-react 库
在生产环境中,请考虑使用 **focus-trap-react** 库。它可以处理所有边界情况,包括影子 DOM、iframe、动态添加的可获得焦点元素,以及按 Escape 键停用。
import FocusTrap from 'focus-trap-react';
{isOpen && (
<FocusTrap>
<div role="dialog" aria-modal="true">
{children}
</div>
</FocusTrap>
)}模态框的 ARIA 属性
将 `role="dialog"` 和 `aria-modal="true"` 添加到模态框容器中。添加指向模态框标题的 `aria-labelledby`。这样可以告诉屏幕阅读器该对话框是一个模态框,并正确播报其内容。
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Confirm Delete</h2>
...
</div>背景的 inert 属性
容器上的 HTML `inert` 属性会将其中的所有元素从焦点顺序和屏幕阅读器可访问性中移除。模态框打开时,将 `document.getElementById('root').inert = true` 设置为 true,以可靠地阻止访问背景内容。
useEffect(() => {
const root = document.getElementById('root');
if (isOpen) root.setAttribute('inert', '');
else root.removeAttribute('inert');
}, [isOpen]);快速检查
键盘用户在焦点陷阱模态框内最后一个 focusable 元素上按 Tab 时,应该发生什么?
回顾
您已经实现了焦点陷阱:查找可获得焦点的元素、拦截 Tab/Shift+Tab、打开时移动 focus,以及关闭时恢复 focus。在生产环境中使用 focus-trap-react,并添加 role='dialog' 和 aria-modal,以支持屏幕阅读器。
接下来
下一课:**使用门户构建工具提示与下拉菜单**——您将使用 createPortal 渲染能够脱离溢出隐藏容器的工具提示和下拉菜单。
常见问题解答
「处理模态框中的焦点与键盘陷阱」课时是免费的吗?
是的 — 「处理模态框中的焦点与键盘陷阱」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。
「处理模态框中的焦点与键盘陷阱」这节课中我会学到什么?
使用 useRef 和事件监听器,让焦点保持在打开的模态框内,以满足无障碍要求。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 React Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「处理模态框中的焦点与键盘陷阱」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 React Academy 课中编写并运行代码吗?
能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。