如何在打开时仅在模式弹出窗口内限制 Tab 键按下?

Posted

技术标签:

【中文标题】如何在打开时仅在模式弹出窗口内限制 Tab 键按下?【英文标题】:How can restrict the tab key press only within the modal popup when its open? 【发布时间】:2018-10-15 03:32:25 【问题描述】:

我打开了一个模式弹出窗口。我有可访问性要求。所以添加了 ARIA 相关的标签。但是当我点击 tab 键时,持续关注到实际页面后面的页面。

在 html 文件中添加了 role="dialog"

但是当模式打开时,我只希望焦点在模式弹出窗口中导航。

正在处理Angular4, html5 项目。 如果我们在 HTML 文件本身中找到解决方案会更好我的意思是没有添加任何 javascript/jQuery 相关的东西来防止这种情况发生

【问题讨论】:

我用香草 javascript 创建了一个库。 npmjs.com/package/focus-trap-js 让我知道它是否适合您。 【参考方案1】:

你问的是焦点陷阱,这个演示很好地展示了它:https://focus-trap.github.io/focus-trap/

添加role="dialog" 不会自动在该元素内提供陷阱焦点。事实上,浏览器并没有原生焦点陷阱

您需要选择以下选项之一:

自己实现焦点陷阱(这方面的好文章:https://hiddedevries.nl/en/blog/2017-01-29-using-javascript-to-trap-focus-in-an-element) 使用焦点陷阱的第 3 方解决方案,例如https://github.com/davidtheclark/focus-trap 使用模态窗口的第 3 方解决方案,例如https://github.com/sweetalert2/sweetalert2完全兼容WAI-ARIA规范,为您提供焦点陷阱

【讨论】:

这些示例似乎非常正确地跟踪键盘,但不处理屏幕阅读器焦点捕获。对话框之外的元素仍然被读取。【参考方案2】:

小心任何仅依赖于 javascript 事件的方法,因为它无法正确处理屏幕阅读器

但是,如果没有 javascript,就像在多个问题中已经指出的那样,这将无法实现,例如 How to keep focus within modal dialog?

您需要完成三个步骤:

1。通过在它们上设置aria-hidden=true 来禁用屏幕阅读器与任何其他节点的交互

例如:

<main aria-hidden="true"><!-- main content here--></main>
<dialog>Your dialog here</dialog>

2。禁用与他们的任何键盘交互

这必须在 Javascript / 或 jQuery 中完成。

这是jQuery中的单行指令,使用jquery-ui

$("main :focusable").addClass("disabled").attr("tabindex", -1);

反过来可以使用:

$(".disabled").removeClass("disabled").attr("tabindex", 0);

3。删除这些元素的任何指针事件以禁用鼠标交互

css 示例:

main[aria-hidden='true']  pointer-events: none;

【讨论】:

这是一个不错的解决方案,但是我相信 :focusable 需要不再维护的 jQuery UI。 Jquery UI 仍在更新中。检查提交日志,本月有提交【参考方案3】:

这是我的解决方案。它根据需要在模态对话框的第一个/最后一个元素上捕获 Tab 或 Shift+Tab(在我的情况下,使用 role="dialog" 找到)。被检查的合格元素都是可见的输入控件,其 HTML 可能是input,select,textarea,button

$(document).on('keydown', function(e) 
    var target = e.target;
    var shiftPressed = e.shiftKey;
    // If TAB key pressed
    if (e.keyCode == 9) 
        // If inside a Modal dialog (determined by attribute role="dialog")
        if ($(target).parents('[role=dialog]').length)                             
            // Find first or last input element in the dialog parent (depending on whether Shift was pressed). 
            // Input elements must be visible, and can be Input/Select/Button/Textarea.
            var borderElem = shiftPressed ?
                                $(target).closest('[role=dialog]').find('input:visible,select:visible,button:visible,textarea:visible').first() 
                             :
                                $(target).closest('[role=dialog]').find('input:visible,select:visible,button:visible,textarea:visible').last();
            if ($(borderElem).length) 
                if ($(target).is($(borderElem))) 
                    return false;
                 else 
                    return true;
                
            
        
    
    return true;
);

【讨论】:

【参考方案4】:

现在 Angular CDK 还提供指令以将焦点陷阱添加到模态弹出窗口https://material.angular.io/cdk/a11y/api#CdkTrapFocus

【讨论】:

【参考方案5】:

父对话框元素应该有role="dialog" 表示这是一个对话框。 在您的情况下,您还缺少aria-modal="true",它应该告诉浏览器和屏幕阅读器焦点应该只停留在模态框内。

使用aria-modal="true" 代替了为应该对屏幕阅读器隐藏的元素添加aria-hidden="true" 并且在模式打开时不会在模式之外接收键盘焦点的需要。

如果上述方法不起作用,您可能仍想为模态外的父元素使用aria-hidden="true",以防止键盘离开模态。

如果由于某种原因仍然无法工作并且您需要手动控制键盘焦点,您可以检查哪些元素在模态之外接收键盘焦点,然后将它们的 tabindex 属性设置为tabindex="-1",这意味着它们仍然可以接收焦点但不是从键盘。在这种方法中,您需要小心,因为当模式关闭时,您需要通过从这些元素中删除 tabindex="-1" 或将其设置回 tabindex="0" 来恢复功能

来源:W3C wai aria practices - dialog modal with example

【讨论】:

【参考方案6】:

仅循环通过模式的输入元素的非 jquery 解决方案


// place this line in the dialog show function - to only add the listener when the dialog is shown
window.addEventListener('keydown', handleKey);

// uncomment and place this in the dialog close/hide function to remove the listener when dialog is closed/hidden
// window.removeEventListener('keydown', handleKey);

function handleKey(e) 
    if (e.keyCode === 9) 
        let focusable = document.querySelector('#modal').querySelectorAll('input,button,select,textarea');
        if (focusable.length) 
            let first = focusable[0];
            let last = focusable[focusable.length - 1];
            let shift = e.shiftKey;
            if (shift) 
                if (e.target === first)  // shift-tab pressed on first input in dialog
                    last.focus();
                    e.preventDefault();
                
             else 
                if (e.target === last)  // tab pressed on last input in dialog
                    first.focus();
                    e.preventDefault();
                
            
        
    

【讨论】:

【参考方案7】:

使用tabindex='-1' 和其他HTML 更改 尝试了不同的解决方案,但在我的情况下没有任何效果,所以这里有一些在我的情况下有效的方法。

第 1 步: 在对话框组件上添加 keydown 事件

  @HostListener('document:keydown', ['$event'])
  handleTabKeyWInModel(event: any) 
       this.sharedService.handleTabKeyWInModel(event, '#modal_id', this.elementRef.nativeElement, 'input,button,select,textarea,a,[tabindex]:not([tabindex="-1"])');
  

这将过滤在模态对话框中预先存在的元素。

第 2 步:在共享服务中处理焦点添加常用方法(或者您也可以将其添加到您的组件中)

handleTabKeyWInModel(e, modelId: string, nativeElement, tagsList: string) 
        if (e.keyCode === 9) 
            const focusable = nativeElement.querySelector(modelId).querySelectorAll(tagsList);
            if (focusable.length) 
               const first = focusable[0];
               const last = focusable[focusable.length - 1];
               const shift = e.shiftKey;
               if (shift) 
                  if (e.target === first)  // shift-tab pressed on first input in dialog
                     last.focus();
                     e.preventDefault();
                  
                 else 
                    if (e.target === last)  // tab pressed on last input in dialog
                        first.focus();
                        e.preventDefault();
                    
                
            
        
    

现在此方法将采用模态对话框原生元素并开始评估每个 tab 键。最后,我们将过滤第一个和最后一个事件,以便我们可以专注于适当的元素(在最后一个元素选项卡点击之后的第一个和第一个元素上的最后一个 shift+tab 事件)。

编码愉快.. :)

【讨论】:

以上是关于如何在打开时仅在模式弹出窗口内限制 Tab 键按下?的主要内容,如果未能解决你的问题,请参考以下文章

检测蓝牙键盘上的 Tab 键按下

C语言 如何在一段时间内检测键盘是不是按下

SQL Server 报告服务:如何在打开时停止报告触发

在Swift中定义[tab]键按下的顺序

鼠标右键按下和放开的键代码是多少

首次打开时,Jquery Mobile AJAX 弹出窗口出现在页面底部