如何在JavaScript中为弹窗实现焦点捕获?
解决弹窗焦点逃逸问题:实现焦点捕获
问题
当前弹窗在键盘导航时,Tab键会将焦点移到弹窗后方的页面元素,需要限制焦点仅在弹窗内的可交互元素(两个操作按钮、关闭按钮)中循环。
实现步骤
- 收集弹窗内可聚焦元素:将弹窗里的按钮、设置了
tabindex="0"的关闭按钮存入数组,用于后续焦点循环处理。 - 监听键盘事件控制焦点循环:给弹窗添加
keydown事件监听,处理Tab/Shift+Tab组合键:- 正向按Tab时,若当前焦点是最后一个可聚焦元素,将焦点跳转到第一个元素;
- 反向按Shift+Tab时,若当前焦点是第一个可聚焦元素,将焦点跳转到最后一个元素;
- 触发上述情况时阻止默认Tab行为,避免焦点跳出弹窗。
- 弹窗显示时自动聚焦:弹窗插入DOM后,自动将焦点移到第一个可聚焦元素(推荐关闭按钮,符合可访问性规范)。
- 弹窗关闭时恢复焦点:记录弹窗打开前的焦点元素,关闭时还原焦点,避免用户失去导航上下文。
修改后的完整代码
// Check if the popup has already been shown during the current session if (!sessionStorage.getItem('popupShown')) { setTimeout(function() { var popup = document.createElement("div"); popup.style.position = "fixed"; popup.style.top = "0"; popup.style.left = "0"; popup.style.zIndex = "999"; popup.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; popup.style.width = "100vw"; popup.style.height = "100vh"; popup.style.display = "flex"; popup.style.alignItems = "center"; popup.style.justifyContent = "center"; // 记录弹窗打开前的焦点元素 const previousFocus = document.activeElement; var innerPopup = document.createElement("div"); innerPopup.style.backgroundColor = "black"; innerPopup.style.display = "flex"; innerPopup.style.flexDirection = "column"; innerPopup.style.alignItems = "center"; innerPopup.style.justifyContent = "center"; innerPopup.style.padding = "40px"; innerPopup.style.margin = "35px"; innerPopup.style.borderRadius = "25px"; var img = document.createElement("img"); img.src = "https://digitalcloud.co.za/wp-content/uploads/2023/01/kerridge_pop_up_illustration.png "; img.alt = "Find out about Kerridge’s core solution "; img.style.width = "25%"; img.style.cursor = "pointer"; innerPopup.appendChild(img); var text = document.createElement("p"); text.innerHTML = "Interested to find out more about our core solution?"; text.style.color = "white"; text.style.marginTop = "50px"; text.style.textAlign = "center"; text.style.fontSize = "20px"; innerPopup.appendChild(text); var button = document.createElement("button"); button.innerHTML = "Book a free demo today"; button.style.backgroundColor = "#E8017B"; button.style.color = "white"; button.style.fontSize = "20px"; button.style.padding = "15px"; button.style.borderRadius = "50px" button.style.border = "none" button.onclick = function() { location.href = "https://digitalcloud.co.za/ "; } innerPopup.appendChild(button); var secondText = document.createElement("p"); secondText.innerHTML = "Stay up to date with the latest Kerridge product updates and existing announcements"; secondText.style.color = "white"; secondText.style.marginTop = "50px"; secondText.style.textAlign = "center"; secondText.style.fontSize = "20px"; innerPopup.appendChild(secondText); var secondButton = document.createElement("button"); secondButton.innerHTML = "Sign up to our Newsletter"; secondButton.style.color = "#E8017B"; secondButton.style.background = "transparent"; secondButton.style.border = "none"; secondButton.style.fontSize = "20px"; secondButton.onclick = function() { location.href = "https://digitalcloud.co.za/ "; } innerPopup.appendChild(secondButton); var closeBtn = document.createElement("div"); closeBtn.style.position = "absolute"; closeBtn.style.top = "50px"; closeBtn.style.right = "50px"; closeBtn.style.cursor = "pointer"; closeBtn.style.width = "60px"; closeBtn.style.height = "60px"; closeBtn.style.borderRadius = "50%"; closeBtn.style.backgroundColor = "#E8017B"; closeBtn.style.display = "flex"; closeBtn.style.alignItems = "center"; closeBtn.style.justifyContent = "center"; closeBtn.setAttribute("tabindex", "0"); closeBtn.setAttribute("role", "button"); closeBtn.setAttribute("aria-label", "Close"); closeBtn.addEventListener("click", function() { popup.remove(); // 恢复之前的焦点 previousFocus.focus(); }); closeBtn.addEventListener("keydown", function(event) { if (event.key === "Enter" || event.key === " ") { popup.remove(); // 恢复之前的焦点 previousFocus.focus(); } }); var closeX = document.createElement("div"); closeX.innerHTML = "X"; closeX.style.color = "white"; closeX.style.fontWeight = "bold"; closeBtn.appendChild(closeX); closeBtn.onclick = function() { popup.remove(); // 恢复之前的焦点 previousFocus.focus(); } innerPopup.appendChild(closeBtn); popup.appendChild(innerPopup); document.body.appendChild(popup); // 收集弹窗内所有可聚焦元素 const focusableElements = popup.querySelectorAll('button, [tabindex="0"]'); const firstFocusable = focusableElements[0]; const lastFocusable = focusableElements[focusableElements.length - 1]; // 弹窗显示后自动聚焦到第一个可聚焦元素 firstFocusable.focus(); // 监听键盘事件处理焦点循环 popup.addEventListener('keydown', function(e) { if (e.key === 'Tab') { // Shift+Tab 反向导航 if (e.shiftKey) { if (document.activeElement === firstFocusable) { e.preventDefault(); lastFocusable.focus(); } } else { // 正向Tab导航 if (document.activeElement === lastFocusable) { e.preventDefault(); firstFocusable.focus(); } } } // ESC键关闭弹窗(提升可访问性) if (e.key === 'Escape') { popup.remove(); previousFocus.focus(); } }); // Set a value in sessionStorage indicating that the popup has been shown sessionStorage.setItem('popupShown', 'true'); }, 90000 ); }
额外说明
- 新增ESC键关闭弹窗的逻辑,符合可访问性最佳实践;
- 焦点循环逻辑仅作用于弹窗内的可交互元素,确保键盘导航不会跳出弹窗范围。
内容的提问来源于stack exchange,提问作者Arya Prem
相关产品推荐
相关产品推荐

