将焦点添加到弹出 /模态时单击以获取选项卡 /可访问性-JavaScript

发布于 2025-01-24 04:40:15 字数 2073 浏览 4 评论 0原文

我有一个元素“单击”上出现的弹出/覆盖层。因为弹出窗口背后有很多HTML内容,所以POP上的按钮/输入元素自然不会具有焦点/Tabindex行为。出于可访问性的原因,我想要它,以便当此弹出我们显示模式中的元素时具有焦点/选项卡索引优先级,而不是其背后的主要内容。

在下面的简单演示中 - 单击“单击我”按钮后,当您使用“选项卡键”时,浏览器仍在覆盖层后面的输入元素中标签。

在显示出显示时如何给出覆盖行为的任何建议将不胜感激。

在模态上创建focus事件似乎不起作用?

codepen: https://codepen.io/anna_papaul/pen/anna_paul/pen/pen/pen/pen/yyywzbzbzbzbz

/strong>

我几乎可以得到乔治·查普曼(George Chapman)的编码词答案,但是当您按住Enter键时,它会在覆盖层出现而不出现之间来回闪烁,并且似乎在Safari中不起作用吗?

let clickMe = document.querySelector('#click-me'),
modal = document.querySelector('.modal'),
closeButton = document.querySelector('.close')

clickMe.addEventListener('click', () => {
  modal.style.display = 'flex';
  // modal.focus();
})

closeButton.addEventListener('click', () => {
  modal.style.display = 'none';
})
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

input, button {
  margin: 1rem;
  padding: .5rem;
}
.click-me {
  display: block;
}

.modal {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100%;   
  justify-content: center;
  align-items: center;
  background: grey;
  position: absolute;
}

form {
  display: flex;
}
<button id="click-me">Click Me</button>
<form action="">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
</form>

<div class="modal">
  <button class="close">Close x</button>
  <button>More Buttons</button>
  <button>More Buttons</button>
</div>

I have a pop-up/overlay that appears on 'click' of an element. Because there is plenty of HTML content behind the pop-up the buttons/input elements on the pop don't naturally have focus/tabindex behaviour. For accessibility reasons I would like it so that when this pop us shows the elements inside the modal have focus/tab index priority not the main content behind it.

In the simple demonstration below - after you click the 'click-me' button, when you use the tab key the browsers still tabs through the input elements behind the overlay.

Any suggestions on how to give the overlay the tab behaviour when it shows would be greatly appreciated.

Creating a focus event on the modal doesn't seem to work?

Codepen: https://codepen.io/anna_paul/pen/eYywZBz

EDIT

I can almost get George Chapman's Codepen answer to work, but when you hold the enter key down it flashes back and forth between the overlay appearing and not appearing, and it doesn't seem to work in Safari?

let clickMe = document.querySelector('#click-me'),
modal = document.querySelector('.modal'),
closeButton = document.querySelector('.close')

clickMe.addEventListener('click', () => {
  modal.style.display = 'flex';
  // modal.focus();
})

closeButton.addEventListener('click', () => {
  modal.style.display = 'none';
})
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

input, button {
  margin: 1rem;
  padding: .5rem;
}
.click-me {
  display: block;
}

.modal {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100%;   
  justify-content: center;
  align-items: center;
  background: grey;
  position: absolute;
}

form {
  display: flex;
}
<button id="click-me">Click Me</button>
<form action="">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
</form>

<div class="modal">
  <button class="close">Close x</button>
  <button>More Buttons</button>
  <button>More Buttons</button>
</div>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

送君千里 2025-01-31 04:40:15

在使模态对话框中访问时,有几件事要考虑,这些内容不仅仅是将焦点放在模态或模态内提取选项卡顺序。您还必须考虑屏幕读取器,如果使用aria-hidded =“ true”,那么您仍然需要感知基础页面元素,然后您也需要取消隐藏将模态关闭并恢复基础页面时的那些元素。

因此,总而言之,您需要做的是:

  1. 将焦点设置为模态内部的第一个焦点元素。
  2. 确保基础页面元素隐藏在屏幕读取器中。
  3. 确保在模态内限制选项卡顺序。
  4. 确保实施预期的键盘行为,例如,按下逃生将关闭或解散模态对话框。
  5. 确保在模态关闭时恢复基础页面元素。
  6. 确保在打开模态对话框之前先前具有焦点的元素已恢复重点。

您还需要确保模态对话框具有ARIA roun =“ Dialog”属性,以便屏幕读取器宣布焦点已移至对话框,理想情况下,您应该使用aria--标记为和/或aria-descred属性为您的模态提供可访问的名称和/或说明。

这是一个很大的列表,但这通常是针对可访问的模态对话框的建议。请参阅 wai-aria模式对话示例

我已经为您的模态写了一个解决方案,部分基于 hidde De Vries的原始代码用于在模态对话框中限制选项卡顺序。

trapfocusinmodal函数函数列出了所有聚焦元素的节点列表,并添加了 tab> tab shift> shift ++ tab 确保焦点的密钥不会超越模式中的焦点元素。密钥侦听器还绑定到逃生键以关闭模态。

OpenModal函数显示模式对话框,隐藏了基本的页面元素,将类名称放在元素上,该元素在打开模式之前上次保持焦点,并将焦点设置为模式中的第一个焦点元素。

cockemotal函数关闭模式,未隐藏基础页面,并恢复在打开模式之前上次保持焦点的元素。

domisReady函数等待DOM准备就绪,然后绑定 Enter 键,然后鼠标单击事件到opentmodalcockemodal < /代码>功能。

codepen: https://codepen.io/gnchapman/pen/pen/pen/pen/jjjmqyop

const KEYCODE_TAB = 9;
const KEYCODE_ESCAPE = 27;
const KEYCODE_ENTER = 13;

// Function to open modal if closed
openModal = function (el) {

    // Find the modal, check that it's currently hidden
    var modal = document.getElementById("modal");
    if (modal.style.display === "") {
        
        // Place class on element that triggered event
        // so we know where to restore focus when the modal is closed
        el.classList.add("last-focus");

        // Hide the background page with ARIA
        var all = document.querySelectorAll("button#click-me,input");
        for (var i = 0; i < all.length; i++) {
            all[i].setAttribute("aria-hidden", "true");
        }
        
        // Add the classes and attributes to make the modal visible
        modal.style.display = "flex";
        modal.setAttribute("aria-modal", "true");
        modal.querySelector("button").focus();
    }
};

// Function to close modal if open
closeModal = function () {

    // Find the modal, check that it's not hidden
    var modal = document.getElementById("modal");
    if (modal.style.display === "flex") {

        modal.style.display = "";
        modal.setAttribute("aria-modal", "false")

        // Restore the background page by removing ARIA
        var all = document.querySelectorAll("button#click-me,input");
        for (var i = 0; i < all.length; i++) {
            all[i].removeAttribute("aria-hidden");
        }
        
        // Restore focus to the last element that had it
        if (document.querySelector(".last-focus")) {
            var target = document.querySelector(".last-focus");
            target.classList.remove("last-focus");
            target.focus();
        }
    }
};

// Function to trap focus inside the modal dialog
// Credit to Hidde de Vries for providing the original code on his website:
// https://hidde.blog/using-javascript-to-trap-focus-in-an-element/
trapFocusInModal = function (el) {

    // Gather all focusable elements in a list
    var query = "a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type='email']:not([disabled]), input[type='text']:not([disabled]), input[type='radio']:not([disabled]), input[type='checkbox']:not([disabled]), select:not([disabled]), [tabindex='0']"
    var focusableEls = el.querySelectorAll(query);
    var firstFocusableEl = focusableEls[0];
    var lastFocusableEl = focusableEls[focusableEls.length - 1];

    // Add the key listener to the modal container to listen for Tab, Enter and Escape
    el.addEventListener('keydown', function(e) {
        var isTabPressed = (e.key === "Tab" || e.keyCode === KEYCODE_TAB);
        var isEscPressed = (e.key === "Escape" || e.keyCode === KEYCODE_ESCAPE);
  
        // Define behaviour for Tab or Shift+Tab
        if (isTabPressed) {
            // Shift+Tab
            if (e.shiftKey) {
                if (document.activeElement === firstFocusableEl) {
                    lastFocusableEl.focus();
                    e.preventDefault();
                }
            }
            
            // Tab
            else {
                if (document.activeElement === lastFocusableEl) {
                    firstFocusableEl.focus();
                    e.preventDefault();
                }
            }
        }
        
        // Define behaviour for Escape
        if (isEscPressed) {
            el.querySelector("button.close").click();
        }
    });
};

// Cross-browser 'DOM is ready' function
// https://www.competa.com/blog/cross-browser-document-ready-with-vanilla-javascript/
var domIsReady = (function(domIsReady) {

    var isBrowserIeOrNot = function() {
        return (!document.attachEvent || typeof document.attachEvent === "undefined" ? 'not-ie' : 'ie');
    }

    domIsReady = function(callback) {
        if(callback && typeof callback === 'function'){
            if(isBrowserIeOrNot() !== 'ie') {
                document.addEventListener("DOMContentLoaded", function() {
                    return callback();
                });
            } else {
                document.attachEvent("onreadystatechange", function() {
                    if(document.readyState === "complete") {
                        return callback();
                    }
                });
            }
        } else {
            console.error('The callback is not a function!');
        }
    }

    return domIsReady;
})(domIsReady || {});


(function(document, window, domIsReady, undefined) {

    // Check if DOM is ready
    domIsReady(function() {

        // Write something to the console
        console.log("DOM ready...");
        
        // Attach event listener on button elements to open modal
        if (document.getElementById("click-me")) {
                
            // Add click listener
            document.getElementById("click-me").addEventListener("click", function(event) {
                // If the clicked element doesn't have the right selector, bail
                if (!event.target.matches('#click-me')) return;
                event.preventDefault();
                // Run the openModal() function
                openModal(event.target);
            }, false);

            // Add key listener
            document.getElementById("click-me").addEventListener('keydown', function(event) {
                if (event.code === "Enter" || event.keyCode === KEYCODE_ENTER) {
                    // If the clicked element doesn't have the right selector, bail
                    if (!event.target.matches('#click-me')) return;
                    event.preventDefault();
                    // Run the openModal() function
                    openModal(event.target);
                }
            });
        }

        // Attach event listener on button elements to close modal
        if (document.querySelector("button.close")) {
                
            // Add click listener
            document.querySelector("button.close").addEventListener("click", function(event) {
                // If the clicked element doesn't have the right selector, bail
                if (!event.target.matches('button.close')) return;
                event.preventDefault();
                // Run the closeModal() function
                closeModal(event.target);
            }, false);

            // Add key listener
            document.querySelector("button.close").addEventListener('keydown', function(event) {
                if (event.code === "Enter" || event.keyCode === KEYCODE_ENTER) {
                    // If the clicked element doesn't have the right selector, bail
                    if (!event.target.matches('button.close')) return;
                    event.preventDefault();
                    // Run the closeModal() function
                    closeModal(event.target);
                }
            });
        }

        // Trap tab order within modal
        if (document.getElementById("modal")) {
            var modal = document.getElementById("modal");
            trapFocusInModal(modal);
        }
        
   });
})(document, window, domIsReady);
<button id="click-me">Click Me</button>
<form action="">
    <input placeholder="An Input" type="text"> <input placeholder="An Input" type="text"> <input placeholder="An Input" type="text"> <input placeholder="An Input" type="text">
</form>
<div class="modal" id="modal" role="dialog">
    <button class="close">Close x</button> <button>More Buttons</button> <button>More Buttons</button>
</div>
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

input, button {
  margin: 1rem;
  padding: .5rem;
}
.click-me {
  display: block;
}

.modal {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100%;   
  justify-content: center;
  align-items: center;
  background: grey;
  position: absolute;
}

form {
  display: flex;
}

There are a few things to consider when making your modal dialog accessible that go beyond just setting focus to your modal or retricting tab order within the modal. You also have to consider that screen readers can still perceive the underlying page elements if they're not hidden from the screen reader using aria-hidden="true", and then you also need to un-hide those elements when the modal is closed and the underlying page is restored.

So, to summarise, what you need to do is:

  1. Set focus to the first focusable element inside the modal when it appears.
  2. Ensure that the underlying page elements are hidden from the screen reader.
  3. Ensure that tab order is restricted inside the modal.
  4. Ensure that expected keyboard behaviour is implemented, e.g., pressing Escape will close or dismiss the modal dialog.
  5. Ensure that the underlying page elements are restored when the modal is closed.
  6. Ensure that the element that previously had focus prior to the modal dialog being opened has focus restored to it.

You also need to ensure that your modal dialog has the ARIA role="dialog" attribute so that screen readers will announce that focus has moved to a dialog, and ideally you should use the aria-labelledby and/or aria-describedby attributes to provide an accessible name and/or description to your modal.

That's quite a list, but it's what is generally recommended for accessible modal dialogs. See the WAI-ARIA Modal Dialog Example.

I've written a solution for your modal, partially based on Hidde de Vries's original code for restricting tab order inside a modal dialog.

The trapFocusInModal function makes a node list of all focusable elements and adds a key listener for Tab and Shift+Tab keys to ensure focus doesn't move beyond the focusable elements in the modal. The key listener also binds to the Escape key to close the modal.

The openModal function displays the modal dialog, hides the underlying page elements, places a class name on the element that last held focus before the modal was opened and sets focus to the first focusable element in the modal.

The closeModal function closes the modal, un-hides the underlying page, and restores focus the element that last held focus before the modal was opened.

The domIsReady function waits for the DOM to be ready and then binds the Enter key and mouse click events to the openModal and closeModal functions.

Codepen: https://codepen.io/gnchapman/pen/JjMQyoP

const KEYCODE_TAB = 9;
const KEYCODE_ESCAPE = 27;
const KEYCODE_ENTER = 13;

// Function to open modal if closed
openModal = function (el) {

    // Find the modal, check that it's currently hidden
    var modal = document.getElementById("modal");
    if (modal.style.display === "") {
        
        // Place class on element that triggered event
        // so we know where to restore focus when the modal is closed
        el.classList.add("last-focus");

        // Hide the background page with ARIA
        var all = document.querySelectorAll("button#click-me,input");
        for (var i = 0; i < all.length; i++) {
            all[i].setAttribute("aria-hidden", "true");
        }
        
        // Add the classes and attributes to make the modal visible
        modal.style.display = "flex";
        modal.setAttribute("aria-modal", "true");
        modal.querySelector("button").focus();
    }
};

// Function to close modal if open
closeModal = function () {

    // Find the modal, check that it's not hidden
    var modal = document.getElementById("modal");
    if (modal.style.display === "flex") {

        modal.style.display = "";
        modal.setAttribute("aria-modal", "false")

        // Restore the background page by removing ARIA
        var all = document.querySelectorAll("button#click-me,input");
        for (var i = 0; i < all.length; i++) {
            all[i].removeAttribute("aria-hidden");
        }
        
        // Restore focus to the last element that had it
        if (document.querySelector(".last-focus")) {
            var target = document.querySelector(".last-focus");
            target.classList.remove("last-focus");
            target.focus();
        }
    }
};

// Function to trap focus inside the modal dialog
// Credit to Hidde de Vries for providing the original code on his website:
// https://hidde.blog/using-javascript-to-trap-focus-in-an-element/
trapFocusInModal = function (el) {

    // Gather all focusable elements in a list
    var query = "a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type='email']:not([disabled]), input[type='text']:not([disabled]), input[type='radio']:not([disabled]), input[type='checkbox']:not([disabled]), select:not([disabled]), [tabindex='0']"
    var focusableEls = el.querySelectorAll(query);
    var firstFocusableEl = focusableEls[0];
    var lastFocusableEl = focusableEls[focusableEls.length - 1];

    // Add the key listener to the modal container to listen for Tab, Enter and Escape
    el.addEventListener('keydown', function(e) {
        var isTabPressed = (e.key === "Tab" || e.keyCode === KEYCODE_TAB);
        var isEscPressed = (e.key === "Escape" || e.keyCode === KEYCODE_ESCAPE);
  
        // Define behaviour for Tab or Shift+Tab
        if (isTabPressed) {
            // Shift+Tab
            if (e.shiftKey) {
                if (document.activeElement === firstFocusableEl) {
                    lastFocusableEl.focus();
                    e.preventDefault();
                }
            }
            
            // Tab
            else {
                if (document.activeElement === lastFocusableEl) {
                    firstFocusableEl.focus();
                    e.preventDefault();
                }
            }
        }
        
        // Define behaviour for Escape
        if (isEscPressed) {
            el.querySelector("button.close").click();
        }
    });
};

// Cross-browser 'DOM is ready' function
// https://www.competa.com/blog/cross-browser-document-ready-with-vanilla-javascript/
var domIsReady = (function(domIsReady) {

    var isBrowserIeOrNot = function() {
        return (!document.attachEvent || typeof document.attachEvent === "undefined" ? 'not-ie' : 'ie');
    }

    domIsReady = function(callback) {
        if(callback && typeof callback === 'function'){
            if(isBrowserIeOrNot() !== 'ie') {
                document.addEventListener("DOMContentLoaded", function() {
                    return callback();
                });
            } else {
                document.attachEvent("onreadystatechange", function() {
                    if(document.readyState === "complete") {
                        return callback();
                    }
                });
            }
        } else {
            console.error('The callback is not a function!');
        }
    }

    return domIsReady;
})(domIsReady || {});


(function(document, window, domIsReady, undefined) {

    // Check if DOM is ready
    domIsReady(function() {

        // Write something to the console
        console.log("DOM ready...");
        
        // Attach event listener on button elements to open modal
        if (document.getElementById("click-me")) {
                
            // Add click listener
            document.getElementById("click-me").addEventListener("click", function(event) {
                // If the clicked element doesn't have the right selector, bail
                if (!event.target.matches('#click-me')) return;
                event.preventDefault();
                // Run the openModal() function
                openModal(event.target);
            }, false);

            // Add key listener
            document.getElementById("click-me").addEventListener('keydown', function(event) {
                if (event.code === "Enter" || event.keyCode === KEYCODE_ENTER) {
                    // If the clicked element doesn't have the right selector, bail
                    if (!event.target.matches('#click-me')) return;
                    event.preventDefault();
                    // Run the openModal() function
                    openModal(event.target);
                }
            });
        }

        // Attach event listener on button elements to close modal
        if (document.querySelector("button.close")) {
                
            // Add click listener
            document.querySelector("button.close").addEventListener("click", function(event) {
                // If the clicked element doesn't have the right selector, bail
                if (!event.target.matches('button.close')) return;
                event.preventDefault();
                // Run the closeModal() function
                closeModal(event.target);
            }, false);

            // Add key listener
            document.querySelector("button.close").addEventListener('keydown', function(event) {
                if (event.code === "Enter" || event.keyCode === KEYCODE_ENTER) {
                    // If the clicked element doesn't have the right selector, bail
                    if (!event.target.matches('button.close')) return;
                    event.preventDefault();
                    // Run the closeModal() function
                    closeModal(event.target);
                }
            });
        }

        // Trap tab order within modal
        if (document.getElementById("modal")) {
            var modal = document.getElementById("modal");
            trapFocusInModal(modal);
        }
        
   });
})(document, window, domIsReady);
<button id="click-me">Click Me</button>
<form action="">
    <input placeholder="An Input" type="text"> <input placeholder="An Input" type="text"> <input placeholder="An Input" type="text"> <input placeholder="An Input" type="text">
</form>
<div class="modal" id="modal" role="dialog">
    <button class="close">Close x</button> <button>More Buttons</button> <button>More Buttons</button>
</div>
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

input, button {
  margin: 1rem;
  padding: .5rem;
}
.click-me {
  display: block;
}

.modal {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100%;   
  justify-content: center;
  align-items: center;
  background: grey;
  position: absolute;
}

form {
  display: flex;
}
骑趴 2025-01-31 04:40:15

出现之后,您必须立即将焦点添加到弹出窗口中时,当您与collectbutton.focus()同时进行时,仅此操作,这就是为什么我使用settimeTimeout( )=&gt;

( 当按下箭头键时,它变得可见,因此我使其可见造型:

      .close:focus {
        border: 2px solid black;
        border-radius: 5px;
      }

整个代码:

      let clickMe = document.querySelector("#click-me"),
        modal = document.querySelector(".modal"),
        closeButton = document.querySelector(".close");

      clickMe.addEventListener("click", () => {
        setTimeout(() => closeButton.focus(), 1);
        modal.style.display = "flex";
      });

      closeButton.addEventListener("click", () => {
        modal.style.display = "none";
      });
      body {
        margin: 0;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }

      input,
      button {
        margin: 1rem;
        padding: 0.5rem;
      }
      .click-me {
        display: block;
      }

      .modal {
        display: none;
        flex-direction: column;
        width: 100%;
        height: 100%;
        justify-content: center;
        align-items: center;
        background: gray;
        position: absolute;
      }

      form {
        display: flex;
      }
      .close:focus {
        border: 2px solid black;
        border-radius: 5px;
      }
    <button id="click-me">Click Me</button>
    <form action="">
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
    </form>

    <div class="modal">
      <button class="close">Close x</button>
      <button>More Buttons</button>
      <button>More Buttons</button>
    </div>

更新:焦点仅在模态内跳跃:

     let clickMe = document.querySelector("#click-me"),
        modal = document.querySelector(".modal"),
        closeButton = document.querySelector(".close");
      lastButton = document.querySelector(".lastButton");
      clickMe.addEventListener("click", () => {
        setTimeout(() => closeButton.focus(), 1);
        modal.style.display = "flex";
      });

      closeButton.addEventListener("click", () => {
        modal.style.display = "none";
      });

      modal.addEventListener("keydown", function (event) {
        var code = event.keyCode || event.which;
        if (code === 9) {
          if (lastButton == document.activeElement) {
            event.preventDefault();
            closeButton.focus();
          }
        }
      });
body {
        margin: 0;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }

      input,
      button {
        margin: 1rem;
        padding: 0.5rem;
      }
      .click-me {
        display: block;
      }

      .modal {
        display: none;
        flex-direction: column;
        width: 100%;
        height: 100%;
        justify-content: center;
        align-items: center;
        background: gray;
        position: absolute;
      }

      form {
        display: flex;
      }
      .close:focus {
        border: 2px solid black;
        border-radius: 5px;
      }
<button id="click-me">Click Me</button>
    <form action="">
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
    </form>

    <div class="modal">
      <button class="close">Close x</button>
      <button>More Buttons</button>
      <button class="lastButton">More Buttons</button>
    </div>

You have to add focus to the pop-up right after this appears, when you do it simultaneously with closeButton.focus() only it won't work that's why I'm using setTimeout(() => closeButton.focus(), 1), this will added it focus after a 1 millisecond.

At first, focus on a button isn't visible, it become visible when arrow keys are pressed, so I make it visible styling it:

      .close:focus {
        border: 2px solid black;
        border-radius: 5px;
      }

The whole code:

      let clickMe = document.querySelector("#click-me"),
        modal = document.querySelector(".modal"),
        closeButton = document.querySelector(".close");

      clickMe.addEventListener("click", () => {
        setTimeout(() => closeButton.focus(), 1);
        modal.style.display = "flex";
      });

      closeButton.addEventListener("click", () => {
        modal.style.display = "none";
      });
      body {
        margin: 0;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }

      input,
      button {
        margin: 1rem;
        padding: 0.5rem;
      }
      .click-me {
        display: block;
      }

      .modal {
        display: none;
        flex-direction: column;
        width: 100%;
        height: 100%;
        justify-content: center;
        align-items: center;
        background: gray;
        position: absolute;
      }

      form {
        display: flex;
      }
      .close:focus {
        border: 2px solid black;
        border-radius: 5px;
      }
    <button id="click-me">Click Me</button>
    <form action="">
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
    </form>

    <div class="modal">
      <button class="close">Close x</button>
      <button>More Buttons</button>
      <button>More Buttons</button>
    </div>

UPDATE: The focus jumps only within the modal:

     let clickMe = document.querySelector("#click-me"),
        modal = document.querySelector(".modal"),
        closeButton = document.querySelector(".close");
      lastButton = document.querySelector(".lastButton");
      clickMe.addEventListener("click", () => {
        setTimeout(() => closeButton.focus(), 1);
        modal.style.display = "flex";
      });

      closeButton.addEventListener("click", () => {
        modal.style.display = "none";
      });

      modal.addEventListener("keydown", function (event) {
        var code = event.keyCode || event.which;
        if (code === 9) {
          if (lastButton == document.activeElement) {
            event.preventDefault();
            closeButton.focus();
          }
        }
      });
body {
        margin: 0;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }

      input,
      button {
        margin: 1rem;
        padding: 0.5rem;
      }
      .click-me {
        display: block;
      }

      .modal {
        display: none;
        flex-direction: column;
        width: 100%;
        height: 100%;
        justify-content: center;
        align-items: center;
        background: gray;
        position: absolute;
      }

      form {
        display: flex;
      }
      .close:focus {
        border: 2px solid black;
        border-radius: 5px;
      }
<button id="click-me">Click Me</button>
    <form action="">
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
      <input type="text" placeholder="An Input" />
    </form>

    <div class="modal">
      <button class="close">Close x</button>
      <button>More Buttons</button>
      <button class="lastButton">More Buttons</button>
    </div>

在巴黎塔顶看东京樱花 2025-01-31 04:40:15

我尝试最简单的解决方案给您。

因此,我的解决方案是:

1 。查看模态中的所有焦点元素。

let modelElement = document.getElementById("modal");
let focusableElements = modelElement.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]');

2 .listen更改对网页的关注。

3 。在“焦点侦听器方法”中,可以检查一下,如果模态是打开的,并且焦点元素不存在于焦点元素列表中,则必须焦点焦点元素列表的第一个元素是焦点。

document.addEventListener('focus', (event) => { 
    if(modal.style.display == 'flex' && !Array.from(focusableElements).includes(event.target))
        Array.from(focusableElements)[0].focus();
}, true);

最终代码:

let clickMe = document.querySelector('#click-me'),
modal = document.querySelector('.modal'),
closeButton = document.querySelector('.close')
console.log(clickMe)


clickMe.addEventListener('click', () =>{
  modal.style.display = 'flex';
  // modal.focus();
})

closeButton.addEventListener('click', () =>{
  modal.style.display = 'none';
})

let modelElement = document.getElementById("modal");
let focusableElements = modelElement.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]');

document.addEventListener('focus', (event) => { 
  if(modal.style.display == 'flex' && !Array.from(focusableElements).includes(event.target))
  Array.from(focusableElements)[0].focus();
}, true);
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

input, button {
  margin: 1rem;
  padding: .5rem;
}
.click-me {
  display: block;
}

.modal {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100%;   
  justify-content: center;
  align-items: center;
  background: grey;
  position: absolute;
}

form {
  display: flex;
}
<button id="click-me">Click Me</button>
<form action="">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
</form>

<div id="modal" class="modal">
  <button class="close">Close x</button>
  <button>More Buttons</button>
  <button>More Buttons</button>
</div>

I trying easiest solution present to you.

So my solution is this:

1.finding all focus-able elements in the modal.

let modelElement = document.getElementById("modal");
let focusableElements = modelElement.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]');

2.listen to change focus on the web page.

3.In the focus listener method, it is checked that if modal is open and focused element not exist in the focus-able elements list, first element of focus-able elements list must be focus.

document.addEventListener('focus', (event) => { 
    if(modal.style.display == 'flex' && !Array.from(focusableElements).includes(event.target))
        Array.from(focusableElements)[0].focus();
}, true);

Final code:

let clickMe = document.querySelector('#click-me'),
modal = document.querySelector('.modal'),
closeButton = document.querySelector('.close')
console.log(clickMe)


clickMe.addEventListener('click', () =>{
  modal.style.display = 'flex';
  // modal.focus();
})

closeButton.addEventListener('click', () =>{
  modal.style.display = 'none';
})

let modelElement = document.getElementById("modal");
let focusableElements = modelElement.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]');

document.addEventListener('focus', (event) => { 
  if(modal.style.display == 'flex' && !Array.from(focusableElements).includes(event.target))
  Array.from(focusableElements)[0].focus();
}, true);
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

input, button {
  margin: 1rem;
  padding: .5rem;
}
.click-me {
  display: block;
}

.modal {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100%;   
  justify-content: center;
  align-items: center;
  background: grey;
  position: absolute;
}

form {
  display: flex;
}
<button id="click-me">Click Me</button>
<form action="">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
  <input type="text" placeholder="An Input">
</form>

<div id="modal" class="modal">
  <button class="close">Close x</button>
  <button>More Buttons</button>
  <button>More Buttons</button>
</div>

傾旎 2025-01-31 04:40:15

将注意力集中在模态中

以将重点放在模态上,您必须将重点放在模态内的聚焦元素上,这就是为什么执行modal.focus();并没有导致焦点进入就像您希望的那样,模态不是模态本身并不是一个焦点元素。取而代之的是,您需要做一些事情,例如$(modal).find(“ button”)。fixp()。focus();而不是。

User2495207向您展示了另一种方法,但是settimeout容易出现错误,并且不必要。理想情况下,我们也不想规定它应该集中在特定按钮上,只要在选项卡顺序中找到的第一个按钮。

但是,这仅解决将重点转移到模态的问题。它不会将焦点捕获到模式中,因此,当您将其选为“最后一个按钮”时,它将焦点转移到模态后面的元素上。

在模式中捕获焦点的

想法是您要检查下一个焦点元素是否在模态内,如果不是在模态之内在模态中。您还应该逆转此逻辑,如果关注第一个按钮并且有人按shift+tab将其包装到模态中的最后一个元素,但是我只是要演示第一个方案:

let clickMe = document.querySelector('#click-me'),
    modal = document.querySelector('.modal'),
    closeButton = document.querySelector('.close')

clickMe.addEventListener('click', () =>{
  modal.style.display = 'flex';
  $(modal).find("button").first().focus();

  trapFocus(modal);
});

function trapFocus(modal) {
  $(modal).find("button").last().on('blur', (e) => {
    // found something outside the modal
    if (!$(modal).find($(e.relatedTarget)).length > 0) {
      e.preventDefault();
      $(modal).find("button").first().focus();
    }
  });
}

closeButton.addEventListener('click', () =>{
  modal.style.display = 'none';
});

<代码>相关图是一个很好的工具,可让您拦截focus事件,以确定焦点的前进。因此,在上面的代码中,我们正在检查即将集中的元素(aka ressedtarget)是否在模态内,如果不是,那么我们将焦点强制焦点。

关于可访问性的最后一个说明

您还希望确保在easse> easse>键盘上关闭模态。在此注释中,e.keycode被弃用,我们都应该使用e.key

如果您需要支持IE,首先,我很抱歉。其次,它需要e.keycode才能正常运行,因此需要与您的e.key检查,例如e.key = ==“逃脱”&amp;&amp; e.KeyCode ===“ 27”。但是,我确实建议您可能只是制作一个将事件作为参数接受的函数,并将这些检查保留在该功能中,因此当IE最终支持e.key时,您可以全部清理代码在一个地方。

Moving focus into the modal

To put focus into the modal, you have to put focus onto a focusable element within the modal, which is why doing modal.focus(); did not result in the focus moving into the modal like you wished since modal itself isn't a focusable element. Instead, you would want to do something such as $(modal).find("button").first().focus(); instead.

User2495207 showed you another way to do this, but setTimeout is prone to bugs and unnecessary. We also ideally don't want to dictate that it should focus on a specific button, just whichever is the first button found in the tab order.

This only solves the problem of moving the focus into the modal initially, however. It does not trap the focus within the modal, so when you tab past the last button, it will move focus to elements behind the modal.

Trapping focus in the modal

The idea here is that you want to check if the next focusable element is within the modal or not, and if not then that means you were on the last element in the modal and need to wrap focus to the first element in the modal. You should also reverse this logic where if the first button is focused and someone presses shift+tab it'll wrap to the last element in the modal, but I am just going to demonstrate the first scenario:

let clickMe = document.querySelector('#click-me'),
    modal = document.querySelector('.modal'),
    closeButton = document.querySelector('.close')

clickMe.addEventListener('click', () =>{
  modal.style.display = 'flex';
  $(modal).find("button").first().focus();

  trapFocus(modal);
});

function trapFocus(modal) {
  $(modal).find("button").last().on('blur', (e) => {
    // found something outside the modal
    if (!$(modal).find($(e.relatedTarget)).length > 0) {
      e.preventDefault();
      $(modal).find("button").first().focus();
    }
  });
}

closeButton.addEventListener('click', () =>{
  modal.style.display = 'none';
});

RelatedTarget is a great tool that allows you to intercept focus events to determine where the focus is going. So in the code above, we are checking if the element that is about to be focused, aka relatedTarget, is within the modal, if it is not, then we force focus where we want it to go.

One last note about Accessibility

You also want to be sure to make the modal close on keydown of Escape. On this note, e.keyCode is deprecated, and we should all be using e.key.

If you need to support IE, first of all, I am sorry. Second of all, it requires e.keyCode to function properly so it needs to be used in conjunction with your e.key check, such as e.key === "Escape" && e.keyCode === "27". I do recommend, however, maybe just making a function that accepts the event as a parameter, and keeping these checks within that function so when IE eventually makes support for e.key then you can cleanup your code all in one spot.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文