如何在模态对话框中保持焦点? [英] How to keep focus within modal dialog?

查看:122
本文介绍了如何在模态对话框中保持焦点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用AngularSemantic-UI开发一个应用程序.该应用程序应该可以访问,这意味着它应该符合WCAG 2.0. 为了达到这个目的,模态应该保持对话框内的焦点,并防止用户走出该模态下页面的元素或在它们之间移动制表符".

我找到了一些可行的示例,如下所示:

这是我尝试使用Semantic-UI创建可访问的模式: https://plnkr.co/edit/HjhkZg

如您所见,我使用了以下属性:

role="dialog"

aria-labelledby="modal-title"

aria-modal="true"

但是他们不能解决我的问题.您是否知道有什么方法可以让我的模态保持焦点,并且仅当用户单击取消/确认"按钮时才会丢失它?

解决方案

当前没有简单的方法可以实现此目的.提出了 inert属性来尝试通过使用具有该属性的所有元素以及所有其他元素来解决此问题.这是孩子们无法接近的地方.但是,采用的速度很慢,直到最近它才降落在Chrome Canary的旗帜后面.

另一种建议的解决方案是制作一个本机API,以跟踪模式堆栈 ,实际上使所有当前不在堆栈顶部的内容变为惰性.我不确定该提案的状态,但是看起来它不会很快实施.

那那把我们留在哪里?

很遗憾,没有一个好的解决方案.一种流行的解决方案是创建所有查询的选择器已知的可聚焦元素,然后通过向模式中的最后一个元素和第一个元素添加击键事件来将焦点捕获到模式中.但是,随着Web组件和影子DOM的兴起,此解决方案可以不再找到所有可聚焦的元素.

如果您始终控制对话框中的所有元素(并且您没有在创建通用对话框库),那么可能最简单的方法是在第一个和最后一个可聚焦元素上添加事件监听器,以进行按键关闭如果使用了制表符或Shift键,则将焦点放在第一个或最后一个元素上以捕获焦点.

如果要创建通用对话框库,我发现唯一可以正常使用的方法是使用惰性polyfill或使模态之外的所有内容都具有tabindex=-1.

 var nonModalNodes;

function openDialog() {    
  var modalNodes = Array.from( document.querySelectorAll('dialog *') );

  // by only finding elements that do not have tabindex="-1" we ensure we don't
  // corrupt the previous state of the element if a modal was already open
  nonModalNodes = document.querySelectorAll('body *:not(dialog):not([tabindex="-1"])');

  for (var i = 0; i < nonModalNodes.length; i++) {
    var node = nonModalNodes[i];

    if (!modalNodes.includes(node)) {

      // save the previous tabindex state so we can restore it on close
      node._prevTabindex = node.getAttribute('tabindex');
      node.setAttribute('tabindex', -1);

      // tabindex=-1 does not prevent the mouse from focusing the node (which
      // would show a focus outline around the element). prevent this by disabling
      // outline styles while the modal is open
      // @see https://www.sitepoint.com/when-do-elements-take-the-focus/
      node.style.outline = 'none';
    }
  }
}

function closeDialog() {

  // close the modal and restore tabindex
  if (this.type === 'modal') {
    document.body.style.overflow = null;

    // restore or remove tabindex from nodes
    for (var i = 0; i < nonModalNodes.length; i++) {
      var node = nonModalNodes[i];
      if (node._prevTabindex) {
        node.setAttribute('tabindex', node._prevTabindex);
        node._prevTabindex = null;
      }
      else {
        node.removeAttribute('tabindex');
      }
      node.style.outline = null;
    }
  }
}
 

I'm developing an app with Angular and Semantic-UI. The app should be accessible, this means it should be compliant with WCAG 2.0. To reach this purpose the modals should keep focus within the dialog and prevents users from going outside or move with "tabs" between elements of the page that lays under the modal.

I have found some working examples, like the following:

Here is my try to create an accessible modal with Semantic-UI: https://plnkr.co/edit/HjhkZg

As you can see I used the following attributes:

role="dialog"

aria-labelledby="modal-title"

aria-modal="true"

But they don't solve my issue. Do you know any way to make my modal keeping focus and lose it only when user click on cancel/confirm buttons?

解决方案

There is currently no easy way to achieve this. The inert attribute was proposed to try to solve this problem by making any element with the attribute and all of it's children inaccessible. However, adoption has been slow and only recently did it land in Chrome Canary behind a flag.

Another proposed solution is making a native API that would keep track of the modal stack, essentially making everything not currently the top of the stack inert. I'm not sure the status of the proposal, but it doesn't look like it will be implemented any time soon.

So where does that leave us?

Unfortunately without a good solution. One solution that is popular is to create a query selector of all known focusable elements and then trap focus to the modal by adding a keydown event to the last and first elements in the modal. However, with the rise of web components and shadow DOM, this solution can no longer find all focusable elements.

If you always control all the elements within the dialog (and you're not creating a generic dialog library), then probably the easiest way to go is to add an event listener for keydown on the first and last focusable elements, check if tab or shift tab was used, and then focus the first or last element to trap focus.

If you're creating a generic dialog library, the only thing I have found that works reasonably well is to either use the inert polyfill or make everything outside of the modal have a tabindex=-1.

var nonModalNodes;

function openDialog() {    
  var modalNodes = Array.from( document.querySelectorAll('dialog *') );

  // by only finding elements that do not have tabindex="-1" we ensure we don't
  // corrupt the previous state of the element if a modal was already open
  nonModalNodes = document.querySelectorAll('body *:not(dialog):not([tabindex="-1"])');

  for (var i = 0; i < nonModalNodes.length; i++) {
    var node = nonModalNodes[i];

    if (!modalNodes.includes(node)) {

      // save the previous tabindex state so we can restore it on close
      node._prevTabindex = node.getAttribute('tabindex');
      node.setAttribute('tabindex', -1);

      // tabindex=-1 does not prevent the mouse from focusing the node (which
      // would show a focus outline around the element). prevent this by disabling
      // outline styles while the modal is open
      // @see https://www.sitepoint.com/when-do-elements-take-the-focus/
      node.style.outline = 'none';
    }
  }
}

function closeDialog() {

  // close the modal and restore tabindex
  if (this.type === 'modal') {
    document.body.style.overflow = null;

    // restore or remove tabindex from nodes
    for (var i = 0; i < nonModalNodes.length; i++) {
      var node = nonModalNodes[i];
      if (node._prevTabindex) {
        node.setAttribute('tabindex', node._prevTabindex);
        node._prevTabindex = null;
      }
      else {
        node.removeAttribute('tabindex');
      }
      node.style.outline = null;
    }
  }
}

这篇关于如何在模态对话框中保持焦点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆