1 回答
TA贡献1797条经验 获得超6个赞
感谢您回答这些问题,希望以下解释能够突出我问他们的原因(然后我将提供一些解决方案)。
为什么我们不能直接拦截tab密钥呢?
屏幕阅读器用户不能仅使用 Tab 键进行导航。根据他们使用的屏幕阅读器,他们使用不同的快捷方式通过标题、链接、表单等进行导航。
这会导致弹出窗口的可访问性问题,因为人们只倾向于捕获密钥tab。然后,如果用户使用快捷方式(例如2在 NVDA 中)跳转页面上的第 2 级标题,他们最终可能会在不知道模态存在的情况下跳到模态之外,而且通常没有任何方法可以在不进行 Tab 切换很长时间的情况下返回到模态。
所以解决方案很明显,确保页面上的其他内容都不可访问(而不仅仅是不可聚焦)。
然而,您需要对 DOM 结构进行良好的排序/组织,以使其易于管理。
需要解决的问题
屏幕阅读器用户可以访问不可聚焦的元素
他们可以更改快捷键,这样我们就不能依靠拦截按键来尝试解决问题。
我们希望保持相同的视觉设计(即我们不能只
display:none
在所有其他元素上使用)。我们想要一种可以重复的模式,这样我们就不能单独隐藏页面上的元素。
我们希望正确管理焦点,以便当模式关闭时它将焦点恢复到上一个项目(在您的情况下)。
我们希望在到达最后一项时循环回到模式中的第一项(我们可以拦截键,tab因为我们无法覆盖所有场景,我们也不希望这样做,因为这会导致更多的可访问性问题。)
解决方案
问题 1、2、3 和 4
由于我们无法拦截按键来管理模态中的焦点,因此我们必须在模态处于活动状态时使所有其他元素(模态中的元素除外)完全不可访问。
aria-hidden="true"
display: none
对于屏幕阅读器来说非常有效。所有屏幕阅读器/浏览器组合的支持率aria-hidden
约为 90% 至 95%。
为了使模态之外的内容无法访问,我们需要添加aria-hidden="true"
到模态之外的每个元素,并tabindex="-1"
确保使用tab键无法将任何内容聚焦到模态之外。
我询问了您的文档结构,因为实现这一点的最简单方法是在区域/主要地标上。
因此,当模态处于活动状态时,我们需要将aria-hidden="true"
和tabindex="-1"
添加到<head>
、等<main>
。<footer>
通过在地标级别执行此操作并将模态放在主文档流之外,这将变得易于管理和维护,同时保留语义 HTML 标记。模态框的情况正好相反(因此当它不活动时使用相同的技术隐藏它。)
模态打开前
<head aria-hidden="false"></head>
<main aria-hidden="false"></main>
<footer aria-hidden="false"></footer>
<div class="modal" aria-hidden="true" tabindex="-1"></div>
模态打开
<head aria-hidden="true" tabindex="-1"></head>
<main aria-hidden="true" tabindex="-1"></main>
<footer aria-hidden="true" tabindex="-1"></footer>
<div class="modal" aria-hidden="false"></div>
请注意我aria-hidden总是如何添加,因为某些屏幕阅读器对动态添加反应不佳aria(尽管它们对更改属性反应良好)。
第 5 点和第 6 点
为此,我认为最简单的方法是分享我用来在模态中捕获焦点的代码。
以下函数的目的是在模式打开时将其聚焦在模式中的第一个可聚焦项目,存储对激活模式的元素的引用(因为我们希望在模式关闭时将用户返回到那里)并管理焦点。
请注意,我使用一个微型库来启用 jQuery 样式选择器,因此您可能需要根据您的使用进行调整。
管理焦点解释和代码
该item变量是在打开模式之前按下的引用按钮(因此我们可以在关闭模式后将焦点返回到那里)。
该className变量是模态的类名,因此您可以针对不同的模态。
kluio.helpers只是我在整个网站上使用的函数数组,因此可以省略。
kluio.globalVars是一个全局变量数组,因此可以代替从函数返回结果。
我为每个部分添加了注释来解释它的作用。
当模态打开时调用该setFocus函数,传递按下激活它的元素和模态的元素className(更适合我们的用例,您可以使用 ID 代替)。
var kluio = {};
kluio.helpers = {};
kluio.globalVars = {};
kluio.helpers.setFocus = function (item, className) { //we pass in the button that activated the modal and the className of the modal, your modal must have a unique className for this to work.
className = className || "content"; //defaults to class 'content' in case of error ("content" being the class on the <main> element.)
kluio.globalVars.beforeOpen = item; //we store the button that was pressed before the modal opened in a global variable so we can return focus to it on modal close.
var focusableItems = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', '[tabindex="0"]']; //a list of items that should be focusable.
var findItems = [];
for (i = 0, len = focusableItems.length; i < len; i++) {
findItems.push('.' + className + " " + focusableItems[i]); //add every focusable item to an array.
}
var findString = findItems.join(", ");
kluio.globalVars.canFocus = Array.prototype.slice.call($('body').find(findString)); //please note we use a custom replacement for jQuery, pretty sure .find() behaves identically but just check it yourself.
if (kluio.globalVars.canFocus.length > 0) {
setTimeout(function () { //set timeout not needed most of the time, we have a modal that is off-screen and slides in, setting focus too early results in the page jumping so we added a delay, you may be able to omit this.
kluio.globalVars.canFocus[0].focus(); //***set the focus to the first focusable element within the modal
kluio.globalVars.lastItem = kluio.globalVars.canFocus[kluio.globalVars.canFocus.length - 1]; //we also store the last focusable item within the modal so we can keep focus within the modal.
}, 600);
}
}
然后,我们keydown使用以下函数拦截该事件来管理焦点。
document.onkeydown = function (evt) {
evt = evt || window.event;
if (evt.keyCode == 27) {
closeAllModals(); //a function that will close any open modal with the Escape key
}
if (kluio.globalVars.modalOpen && evt.keyCode == 9) { //global variable to check any modal is open and key is the tab key
if (evt.shiftKey) { //also pressing shift key
if (document.activeElement == kluio.globalVars.canFocus[0]) { //the current element is the same as the first focusable element
evt.preventDefault();
kluio.globalVars.lastItem.focus(); //we focus the last focusable element as we are reverse tabbing through the items.
}
} else {
if (document.activeElement == kluio.globalVars.lastItem) { //when tabbing forward we look for the last tabbable element
evt.preventDefault();
kluio.globalVars.canFocus[0].focus(); //move the focus to the first tabbable element.
}
}
}
};
最后,在您的 closeAllModals 函数版本中,您需要将焦点返回到引用元素/按钮。
if (kluio.globalVars.beforeOpen) {
kluio.globalVars.beforeOpen.focus();
}
一旦激活该行,kluio.globalVars.canFocus[0].focus(); 就会调用该行将焦点设置到模式中的第一个可聚焦项目,您不需要在第一个元素打开时按 Tab 键,它应该自动聚焦。
点 5 被线覆盖,kluio.globalVars.beforeOpen = item;以设置对打开模式的项目的引用,并kluio.globalVars.beforeOpen.focus();在关闭函数内将焦点返回到该项目。
第 6 点包含在document.onkeydown从 开始的函数中if (kluio.globalVars.modalOpen && evt.keyCode == 9) {。
我希望以上所有内容都清楚,有任何问题尽管问,如果以后有时间我会把它变成一个小提琴。
- 1 回答
- 0 关注
- 73 浏览
添加回答
举报