如何将 Javascript 中的焦点捕获到弹出窗口?

问题描述 投票:0回答:3

我为需要可访问的弹出窗口创建了以下 JavaScript 代码。

问题在于,在键盘浏览期间,选项卡按钮首先选择登陆页面上弹出窗口后面的所有按钮。我需要它只选择弹出窗口上的按钮和关闭按钮。

我知道焦点捕获弹出窗口是一种可能有效的方法,但我不知道如何将其添加到我的代码中。

https://codepen.io/aryanotstark/pen/KKBjLXY << Here is the code

https://digitalcloud.co.za/kiron/ << You can view the popup in action on this website

<script>
    // 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";
            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() {
  // Add logic to close something here
  popup.remove();
});

closeBtn.addEventListener("keydown", function(event) {
  if (event.key === "Enter" || event.key === " ") {
    // Add logic to close something here
    popup.remove();
  }
});

            var closeX = document.createElement("div");
            closeX.innerHTML = "X";
            closeX.style.color = "white";
            closeX.style.fontWeight = "bold";

            closeBtn.appendChild(closeX);
            closeBtn.onclick = function() {
                popup.remove();
            }
            innerPopup.appendChild(closeBtn);

            popup.appendChild(innerPopup);
            document.body.appendChild(popup);
            // Set a value in sessionStorage indicating that the popup has been shown
            sessionStorage.setItem('popupShown', 'true');
        }, 90000 );
    }
</script>

javascript wordpress accessibility
3个回答
2
投票

为了使问题更完整,对于 modal 对话框,您需要两件事:

  1. 将焦点捕获在模式对话框内
  2. 隐藏辅助技术中的所有其他内容

当然还有更多内容,但这是与问题相关的部分。

通过 Tab 导航只是屏幕阅读器的一种导航方式。 Android 和 iOS 上的屏幕阅读器 TalkBack 和 VoiceOver 在实际焦点和阅读焦点之间没有区别。这就是后者的含义:避免读取对话框之外的内容。

在很多解决方案中,这做得很糟糕,并且可以移出移动平台上的模式对话框,例如在 Bootstrap 5 中。

原生、标准方法

标准化了

<dialog>
元素来解决这些问题。

显然,这是最好的方法,因为这意味着您拥有浏览器供应商开发的所有动力来支持您的对话框。

根据 Scott O'hara 两周前的说法,你应该使用它

如果您不想依赖此本机元素并实现自定义模式对话框,则本机元素会根据您的问题执行两件事。

<dialog>

 方法调用的 
showModal()
元素将具有隐式
aria-modal="true"
[…]

[…] 除了

<dialog>
及其内容之外的所有内容都应使用
inert
属性呈现惰性。

换句话说,这将引导我们

<main inert>
  …
  <p><a href="#">Can you focus me?</a></p>
</main>
<div role="dialog" aria-modal="true" aria-labelledby="d-title">
  <h2 id="d-title">Dialog title</h2>
  …
  <p><button>Can you focus me?</button></p>
</div>

不幸的是,浏览器支持还不是很好。

替代
aria-modal

该属性应该对辅助技术隐藏所有其他内容(上例中的

<main>
)。对于不支持此功能的浏览器,
aria-hidden
可以应用于其他内容:

<main aria-hidden="true">
…
</main>
<div role="dialog" …>

这不会影响注意力,但会影响通过辅助技术阅读。

陷阱焦点

有两种策略。

拦截
focusin

这是Bootstrap的模态APG的模态对话框示例中使用的方法。

一种策略是将侦听器绑定到对话框外部内容的focusin

事件
,并将焦点放回到对话框中的第一个或最后一个交互元素上,具体取决于最后一个焦点。

您不能使用

focusout

事件,因为用户触发的
focus焦点顺序
将在
focusout
之后触发,并且后者不可取消。

document.getElementsByTagName('main')[0].addEventListener('focusin', () => document.querySelector('[role=dialog] button').focus());
<main>
  …
  <p><a href="#">Can you focus me?</a></p>
</main>
<div role="dialog" aria-modal="true" aria-labelledby="d-title">
  <h2 id="d-title">Dialog title</h2>
  …
  <p><button autofocus>Can you focus me?</button></p>
</div>

使其他一切都无法聚焦

您可以尝试渲染所有其他交互元素

不可聚焦可点击

这显然更加脆弱,特别是如果交互性不是通过使用本机 HTML 元素或 ARIA 角色来公开的,因此很难识别它们。

<main style="pointer-events: none"> … <p><a href="#" tabindex="-1">Can you focus me?</a></p> </main> <div role="dialog" aria-modal="true" aria-labelledby="d-title"> <h2 id="d-title">Dialog title</h2> … <p><button autofocus>Can you focus me?</button></p> </div>


0
投票
你可以用js逻辑动态添加With

<div id="div1" tabindex="-1"> content you'd like to prevent tab</div>


    


0
投票
一个很酷的想法是在对话框之前和之后附加隐藏的、可选项卡的元素。一旦它们获得焦点,将其传递给对话框内的第一个或最后一个元素:

[PREV HANDLE] DIALOG ----- | | | INPUT A | | INPUT B | | CHECKBOX | | BUTTON | ------------ [NEXT HANDLE]
伪代码如下:

<div tabindex="0" class="hidden" /> <div role="dialog" aria-modal="true"> <input /> <button /> </div> <div tabindex="0" class="hidden" id="nextHandle" />
const trapFocus = (e) => {
  const isDialogBlured = !dialogElement.contains(e.target);

  if (isDialogBlured) {
    document.activeElement === nextHandle
      ? focusFirstDescendant(dialogElement) // Tab clicked - focus first descendant
      : focusLastDescendant(dialogElement); // Shift + Tab clicked - focus last descendant
  }
}

document.addEventListener("focus", trapFocus, true);
focusFirstDescendant(dialogElement);
示例片段,基于 

https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/

const wrapper = document.getElementById("wrapper"); const dialog = document.getElementById("dialog"); const triggerButton = document.getElementById("trigger"); const nextHandle = document.getElementById("nextHandle"); let preventFocusTrap = false; const showDialog = () => { document.addEventListener("focus", trapFocus, true); wrapper.classList.remove("hidden"); focusFirstDescendant(dialog); }; const closeDialog = () => { document.removeEventListener("focus", trapFocus, true); wrapper.classList.add("hidden"); triggerButton.focus(); } const attemptFocus = (element) => { if (!isFocusable(element)) { return false; } preventFocusTrap = true; // We're about to manually focus element inside dialog. Tell the event handler not to take action element.focus(); preventFocusTrap = false; return document.activeElement === element; }; const focusFirstDescendant = (element) => { for (let i = 0; i < element.childNodes.length; i++) { const child = element.childNodes[i]; if (attemptFocus(child) || focusFirstDescendant(child)) { return true; // escape recursive function } } }; const focusLastDescendant = (element) => { for (let i = element.childNodes.length - 1; i >= 0; i--) { const child = element.childNodes[i]; if (attemptFocus(child) || focusLastDescendant(child)) { return true; // escape recursive function } } }; const trapFocus = (e) => { if (preventFocusTrap) { return; } const isDialogBlured = !dialog.contains(e.target); if (isDialogBlured) { document.activeElement === nextHandle ? focusFirstDescendant(dialog) // Tab clicked - focus first descendant : focusLastDescendant(dialog); // Shift + Tab clicked - focus last descendant } }; const isFocusable = (element) => { if (element.tabIndex < 0) { return false; } if (element.disabled) { return false; } switch (element.nodeName) { case "A": return !!element.href && element.rel !== "ignore"; case "INPUT": return element.type !== "hidden"; case "BUTTON": case "SELECT": case "TEXTAREA": return true; default: return false; } };
.hidden {
  display: none;
}

/* http://a11yproject.com/posts/how-to-hide-content/ */
.invisible {
  position: absolute;
  width: 0;
  height: 0;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

/* Visual styles, insignificant */

#dialog {
  border: 2px solid #14acca;
  background-color: #eef8ff;
  border-radius: 10px;
  margin: 10px 0;
  padding: 0 15px;
}

a, button, label {
  display: block;
  margin: 10px 0;
}
<button onclick="showDialog()" id="trigger">
  Display dialog and trap focus
</button>

<div id="wrapper" class="hidden">
  <div tabindex="0" class="invisible">Prev handle</div>
  <div role="dialog" aria-modal="true" id="dialog">
    <h3>Hi! I am a dialog. Hit Tab or Shift+Tab few times to test focus trap</h3>
    <label>User: <input type="text" /></label>
    <label>Password: <input type="text" /></label>
    <label><input type="checkbox" /> Remember me</label>
    <button onclick="closeDialog()">Close dialog and untrap focus</button>
  </div>
  <div tabindex="0" class="invisible" id="nextHandle">Next handle</div>
</div>

<a href="#">Link after dialog</a>
<button>Button after dialog</button>

© www.soinside.com 2019 - 2024. All rights reserved.