1 回答

TA貢獻(xiàn)1797條經(jīng)驗(yàn) 獲得超6個(gè)贊
感謝您回答這些問題,希望以下解釋能夠突出我問他們的原因(然后我將提供一些解決方案)。
為什么我們不能直接攔截tab密鑰呢?
屏幕閱讀器用戶不能僅使用 Tab 鍵進(jìn)行導(dǎo)航。根據(jù)他們使用的屏幕閱讀器,他們使用不同的快捷方式通過標(biāo)題、鏈接、表單等進(jìn)行導(dǎo)航。
這會(huì)導(dǎo)致彈出窗口的可訪問性問題,因?yàn)槿藗冎粌A向于捕獲密鑰tab。然后,如果用戶使用快捷方式(例如2在 NVDA 中)跳轉(zhuǎn)頁(yè)面上的第 2 級(jí)標(biāo)題,他們最終可能會(huì)在不知道模態(tài)存在的情況下跳到模態(tài)之外,而且通常沒有任何方法可以在不進(jìn)行 Tab 切換很長(zhǎng)時(shí)間的情況下返回到模態(tài)。
所以解決方案很明顯,確保頁(yè)面上的其他內(nèi)容都不可訪問(而不僅僅是不可聚焦)。
然而,您需要對(duì) DOM 結(jié)構(gòu)進(jìn)行良好的排序/組織,以使其易于管理。
需要解決的問題
屏幕閱讀器用戶可以訪問不可聚焦的元素
他們可以更改快捷鍵,這樣我們就不能依靠攔截按鍵來嘗試解決問題。
我們希望保持相同的視覺設(shè)計(jì)(即我們不能只
display:none
在所有其他元素上使用)。我們想要一種可以重復(fù)的模式,這樣我們就不能單獨(dú)隱藏頁(yè)面上的元素。
我們希望正確管理焦點(diǎn),以便當(dāng)模式關(guān)閉時(shí)它將焦點(diǎn)恢復(fù)到上一個(gè)項(xiàng)目(在您的情況下)。
我們希望在到達(dá)最后一項(xiàng)時(shí)循環(huán)回到模式中的第一項(xiàng)(我們可以攔截鍵,tab因?yàn)槲覀儫o法覆蓋所有場(chǎng)景,我們也不希望這樣做,因?yàn)檫@會(huì)導(dǎo)致更多的可訪問性問題。)
解決方案
問題 1、2、3 和 4
由于我們無法攔截按鍵來管理模態(tài)中的焦點(diǎn),因此我們必須在模態(tài)處于活動(dòng)狀態(tài)時(shí)使所有其他元素(模態(tài)中的元素除外)完全不可訪問。
aria-hidden="true"
display: none
對(duì)于屏幕閱讀器來說非常有效。所有屏幕閱讀器/瀏覽器組合的支持率aria-hidden
約為 90% 至 95%。
為了使模態(tài)之外的內(nèi)容無法訪問,我們需要添加aria-hidden="true"
到模態(tài)之外的每個(gè)元素,并tabindex="-1"
確保使用tab鍵無法將任何內(nèi)容聚焦到模態(tài)之外。
我詢問了您的文檔結(jié)構(gòu),因?yàn)閷?shí)現(xiàn)這一點(diǎn)的最簡(jiǎn)單方法是在區(qū)域/主要地標(biāo)上。
因此,當(dāng)模態(tài)處于活動(dòng)狀態(tài)時(shí),我們需要將aria-hidden="true"
和tabindex="-1"
添加到<head>
、等<main>
。<footer>
通過在地標(biāo)級(jí)別執(zhí)行此操作并將模態(tài)放在主文檔流之外,這將變得易于管理和維護(hù),同時(shí)保留語(yǔ)義 HTML 標(biāo)記。模態(tài)框的情況正好相反(因此當(dāng)它不活動(dòng)時(shí)使用相同的技術(shù)隱藏它。)
模態(tài)打開前
<head aria-hidden="false"></head>
<main aria-hidden="false"></main>
<footer aria-hidden="false"></footer>
<div class="modal" aria-hidden="true" tabindex="-1"></div>
模態(tài)打開
<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>
請(qǐng)注意我aria-hidden總是如何添加,因?yàn)槟承┢聊婚喿x器對(duì)動(dòng)態(tài)添加反應(yīng)不佳a(bǔ)ria(盡管它們對(duì)更改屬性反應(yīng)良好)。
第 5 點(diǎn)和第 6 點(diǎn)
為此,我認(rèn)為最簡(jiǎn)單的方法是分享我用來在模態(tài)中捕獲焦點(diǎn)的代碼。
以下函數(shù)的目的是在模式打開時(shí)將其聚焦在模式中的第一個(gè)可聚焦項(xiàng)目,存儲(chǔ)對(duì)激活模式的元素的引用(因?yàn)槲覀兿M谀J疥P(guān)閉時(shí)將用戶返回到那里)并管理焦點(diǎn)。
請(qǐng)注意,我使用一個(gè)微型庫(kù)來啟用 jQuery 樣式選擇器,因此您可能需要根據(jù)您的使用進(jìn)行調(diào)整。
管理焦點(diǎn)解釋和代碼
該item變量是在打開模式之前按下的引用按鈕(因此我們可以在關(guān)閉模式后將焦點(diǎn)返回到那里)。
該className變量是模態(tài)的類名,因此您可以針對(duì)不同的模態(tài)。
kluio.helpers只是我在整個(gè)網(wǎng)站上使用的函數(shù)數(shù)組,因此可以省略。
kluio.globalVars是一個(gè)全局變量數(shù)組,因此可以代替從函數(shù)返回結(jié)果。
我為每個(gè)部分添加了注釋來解釋它的作用。
當(dāng)模態(tài)打開時(shí)調(diào)用該setFocus函數(shù),傳遞按下激活它的元素和模態(tài)的元素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使用以下函數(shù)攔截該事件來管理焦點(diǎn)。
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 函數(shù)版本中,您需要將焦點(diǎn)返回到引用元素/按鈕。
if (kluio.globalVars.beforeOpen) {
kluio.globalVars.beforeOpen.focus();
}
一旦激活該行,kluio.globalVars.canFocus[0].focus(); 就會(huì)調(diào)用該行將焦點(diǎn)設(shè)置到模式中的第一個(gè)可聚焦項(xiàng)目,您不需要在第一個(gè)元素打開時(shí)按 Tab 鍵,它應(yīng)該自動(dòng)聚焦。
點(diǎn) 5 被線覆蓋,kluio.globalVars.beforeOpen = item;以設(shè)置對(duì)打開模式的項(xiàng)目的引用,并kluio.globalVars.beforeOpen.focus();在關(guān)閉函數(shù)內(nèi)將焦點(diǎn)返回到該項(xiàng)目。
第 6 點(diǎn)包含在document.onkeydown從 開始的函數(shù)中if (kluio.globalVars.modalOpen && evt.keyCode == 9) {。
我希望以上所有內(nèi)容都清楚,有任何問題盡管問,如果以后有時(shí)間我會(huì)把它變成一個(gè)小提琴。
- 1 回答
- 0 關(guān)注
- 112 瀏覽
添加回答
舉報(bào)