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 關注
- 105 瀏覽
添加回答
舉報