我一直试图弄清楚如何将屏幕阅读器焦点包含在特定区域内。当我说屏幕阅读器焦点时,我并不是说可以通过按Tab键/ Shift-Tab键移动的默认浏览器焦点。我主要在Mac上使用Voiceover时实现了可访问性,当您打开它时,页面上会出现一个新的焦点框,并读出“突出显示”的信息。
此时,如果要使用Tab键,浏览器和屏幕阅读器的焦点将同时移动。除了制表符以外,您还可以按住cmd + opt和左右键来将屏幕阅读器的焦点从一个元素移到另一个元素,无论是否可以将其切换到该元素。这就是我要包含的重点。
当焦点位于我要聚焦的最后一个元素上时,我尝试防止按cmd,opt和箭头键,但是浏览器似乎无法识别屏幕阅读器的焦点。而且我认为禁用键盘无论如何都不能与屏幕阅读器一起使用,因为它似乎独立于浏览器工作。
我还尝试过在出现模式时对页面上的所有其他元素动态添加tabindex:-1和aria-hidden:true。事后打开“画外音”时,此功能有效;屏幕阅读器的焦点实际上确实被困住了。但是,如果首先打开屏幕阅读器(在大多数用户实例中可能是这种情况),则屏幕阅读器将不考虑动态变化。就像屏幕阅读器在页面加载时获取可访问性状态的“快照”一样,它不考虑对DOM的新更改。
有人有什么想法吗?
您无法阻止使用屏幕阅读器的快捷键。他们优先于其他一切。它们甚至不会被脚本中的keydown / up / press处理程序捕获。对于我们作为屏幕阅读器用户来说,幸运的是,这不是可接受的方法。
您也已经观察到,浏览光标实际上完全独立于系统焦点。可访问性树确定使用屏幕阅读器的浏览光标时可以访问的内容。
要临时限制浏览光标所看到的元素,必须使用aria-modal属性。将其放在应该可到达的根元素上。里面的所有东西都将保持可达。只要属性保留在元素上,外部其他所有内容都将无法访问。
请勿与隐藏的咏叹调玩耍以产生相同的效果。一些屏幕阅读器会遇到具有aria-hidden属性的嵌套元素的问题。例如,如果外部元素具有aria-hidden = true,而内部元素具有aria-hidden = false,则下颌将不会显示内部元素。
用aria-modal限制浏览光标,以及用aria-hidden来隐藏元素,并不自动暗示它们不能以常规系统焦点(Tab / Shift + Tab)进行聚焦。因此,您通常会使用焦点陷阱将aria-modal限制加倍,以防止系统焦点移到预期之外的地方。如果不这样做,可能会给屏幕阅读器用户带来麻烦(如果焦点当前位于可访问性树中隐藏的元素上,屏幕阅读器应该怎么办?)。这是经常性的监督。
进行焦点陷阱的最安全方法是在最后一个允许的元素上捕获选项卡,在第一个允许的元素上捕获Shift + Tab,然后分别。将焦点返回到允许的第一个或最后一个元素。比将所有可聚焦元素设置为tabindex = -1然后再返回tabindex = 0容易得多,据我测试,它几乎可以在任何地方使用。
这是我用来管理模态焦点的方法。
item
变量是在打开模态之前已按下的引用按钮(因此我们可以在关闭模态后返回焦点。)>
className
变量是模态的类名,因此您可以定位不同的模态。
[kluio.helpers
只是我在网站上使用的一系列功能,因此可以省略。
kluio.globalVars
是全局变量的数组,因此可以用来代替从函数返回结果的方法。
我已在每个部分中添加注释以解释其功能。
setFocus
函数在打开模态时会被调用,传入被按下以激活它的元素和模态的className(在我们的使用案例中更好,请改用ID)。
var kluio = {}; kluio.helpers = {}; kluio.globalVars = {}; kluio.helpers.setFocus = function (item, className) { className = className || "content"; //defaults to class 'content' in case of error (content being 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)); 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. 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(); }
[它对于我们的用例很好用,尽管它可以做得很整洁,并且我们有一些奇怪的做法(全局变量...。我保证有很好的理由!)它有望对您有用。