移动端(Android/iOS)模态框焦点循环捕获问题及方案咨询
移动端模态框焦点循环捕获实现方案
问题核心
移动端浏览器(iOS Safari、Chrome移动端等)的焦点逻辑和桌面端不同,原生焦点导航会跳出模态框到浏览器控件(地址栏、底部导航等),需要通过额外逻辑强制焦点在模态框内循环。
可行方案
方案一:手动实现焦点拦截逻辑
通过监听keydown和focusout事件,强制焦点在模态框内的可聚焦元素间循环,同时处理屏幕阅读器的焦点导航行为。
步骤与代码示例
- 定义基础元素与变量
const modal = document.getElementById('your-modal'); // 筛选模态框内所有可聚焦元素 const focusableElements = modal.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const firstFocusableEl = focusableElements[0]; const lastFocusableEl = focusableElements[focusableElements.length - 1]; let previouslyFocusedElement; // 保存模态框打开前的焦点元素
- 模态框打开时初始化
function openModal() { previouslyFocusedElement = document.activeElement; modal.style.display = 'block'; // 延迟聚焦确保模态框渲染完成 setTimeout(() => firstFocusableEl.focus(), 100); // 绑定事件监听 modal.addEventListener('keydown', handleKeydown); modal.addEventListener('focusout', handleFocusOut); }
- 处理Tab键循环逻辑
function handleKeydown(e) { if (e.key === 'Tab') { // Shift+Tab 从第一个元素跳转到最后一个 if (e.shiftKey && document.activeElement === firstFocusableEl) { e.preventDefault(); lastFocusableEl.focus(); } // Tab 从最后一个元素跳转到第一个 else if (!e.shiftKey && document.activeElement === lastFocusableEl) { e.preventDefault(); firstFocusableEl.focus(); } } // ESC键关闭模态框 else if (e.key === 'Escape') { closeModal(); } }
- 拦截焦点跳出模态框
function handleFocusOut(e) { // 检查新焦点是否在模态框外部 if (!modal.contains(e.relatedTarget)) { e.preventDefault(); // 根据当前焦点位置,切换到对应端点元素 document.activeElement === lastFocusableEl ? firstFocusableEl.focus() : lastFocusableEl.focus(); } }
- 关闭模态框时恢复焦点
function closeModal() { modal.style.display = 'none'; // 移除事件监听 modal.removeEventListener('keydown', handleKeydown); modal.removeEventListener('focusout', handleFocusOut); // 恢复之前的焦点 if (previouslyFocusedElement) previouslyFocusedElement.focus(); }
额外注意事项
- 给模态框添加
role="dialog"和aria-modal="true"属性,帮助屏幕阅读器识别模态框,避免导航到外部内容。 - 确保打开模态框的函数绑定在用户交互事件(如按钮点击)内,iOS Safari要求
focus()触发必须在用户交互上下文里。 - 在真实移动设备上测试,模拟器的焦点行为可能与真机存在差异。
方案二:使用成熟的无障碍库
推荐使用focus-trap库,它已内置处理移动端焦点异常的逻辑,无需手动编写复杂代码。
代码示例
- 安装依赖
npm install focus-trap
- 实现焦点捕获
import focusTrap from 'focus-trap'; const modal = document.getElementById('your-modal'); // 初始化焦点捕获 const trap = focusTrap(modal, { onDeactivate: () => { // 关闭时恢复原焦点 if (previouslyFocusedElement) previouslyFocusedElement.focus(); } }); function openModal() { previouslyFocusedElement = document.activeElement; modal.style.display = 'block'; trap.activate(); // 激活焦点捕获 } function closeModal() { modal.style.display = 'none'; trap.deactivate(); // 停用焦点捕获 }
总结
- 手动实现适合需要精细控制场景,但需覆盖更多边缘情况;
- 使用
focus-trap库更高效可靠,已适配绝大多数移动端浏览器的焦点行为。
内容的提问来源于stack exchange,提问作者Bhagyashri
相关产品推荐
相关产品推荐

