Как предотвратить фокусировку программы чтения с экрана (не относящуюся к фокусу клавиатуры) из предопределенной области (например, модальной) - javascript


0

Я пытался выяснить, как удерживать фокус чтения с экрана в определенной области. Когда я говорю о фокусировке программы чтения с экрана, я не имею в виду фокус браузера по умолчанию, который можно перемещать с помощью табуляции/сдвига. Я преимущественно реализую специальные возможности при использовании Voiceover на Mac, и при его включении на странице появляется новое поле фокусировки, которое считывает информацию, которую он "выделяет".

В этот момент, если вы переходите на вкладку, и браузер, и фокус чтения с экрана перемещаются одновременно. Помимо вкладок к различным фокусируемым элементам, вы также можете удерживать cmd + opt и нажатие клавиш влево и вправо для перемещения фокуса программы чтения с экрана от элемента к элементу, независимо от того, можно ли на него перейти. Это фокус, который я пытаюсь сдержать.

Я пытался предотвратить нажатие клавиш cmd, opt и arrow, когда фокус находится на последнем элементе, который я хочу сфокусировать, но браузер, похоже, не распознает фокус чтения с экрана. И я считаю, что отключение клавиатуры в любом случае не будет работать с программой чтения с экрана, так как она работает независимо от браузера.

Я также пытался динамически добавлять tabindex: -1 и aria-hidden: true ко всем другим элементам на странице, когда появляется модальное окно. Это работает, когда вы включаете Voiceover после факта; фокус чтения с экрана действительно оказывается в ловушке. Однако, если программа чтения с экрана включена, что, скорее всего, будет иметь место в большинстве случаев, программа чтения с экрана не учитывает динамические изменения. Это похоже на то, как программа чтения с экрана делает "снимок" состояния доступности при загрузке страницы и не учитывает новые изменения в DOM.

У кого-нибудь есть идеи?

  •  48
  •  2
  • 11 май 2020 2020-05-11 10:17:11

2 ответа

0

Вот метод, который я использую для управления фокусом для модалов, который решает вашу проблему.

Переменная item - это ссылка на кнопку, которая была нажата перед открытием модального окна (поэтому мы можем вернуть фокус после закрытия модального окна).

Переменная className - это имя класса модала, поэтому вы можете использовать разные модалы.

kluio.helpers - это просто набор функций, которые я использую на сайте, поэтому его можно опустить.

kluio.globalVars - это массив глобальных переменных, поэтому его можно заменить на возврат результатов из функции.

Я добавил комментарии к каждой части, чтобы объяснить, что она делает.

Функция setFocus вызывается, когда модал открывается, передавая элемент, который был нажат, чтобы активировать его, и модальное имя класса (работает для нашего варианта использования лучше, вместо него следует использовать идентификатор).

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)); //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.
            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();
}

Это хорошо работает для нашего варианта использования, и хотя это может быть полезно для аккуратности, и у нас есть несколько странных практик (глобальные переменные... у нас есть веские причины, я обещаю!), мы надеемся, что они пригодятся вам.

1

Вы не можете предотвратить использование сочетаний клавиш программы чтения с экрана. Они имеют приоритет над всем остальным. Они даже не попадают в обработчик нажатия клавиш/вверх/нажатия в вашем скрипте. К счастью для нас, как пользователей программ чтения с экрана, этот путь неприемлем.

Как вы также заметили, курсор обзора фактически полностью независим от системного фокуса. Дерево доступности определяет, чего можно достичь при использовании курсора просмотра экрана.

Чтобы временно ограничить элементы, видимые курсором обзора, вы должны использовать атрибут aria-modal. Поместите это в корневой элемент, который должен быть достижимым. Все внутри останется достижимым. Все остальное снаружи больше не будет доступно, пока атрибут остается на элементе.

Не играйте со скрытой арией, чтобы получить тот же эффект. Некоторые программы чтения с экрана имеют проблемы с вложенными элементами, имеющими скрытый атрибут aria. Например, если внешний элемент имеет aria-hidden = true, а внутренний элемент имеет aria-hidden = false, Jaws не будет отображать внутренний элемент.

Ограничение курсора обзора с помощью aria-modal, а также, кстати, скрытие элементов с помощью aria-hidden, автоматически не означает, что они не могут быть сфокусированы с помощью обычной системной фокусировки (Tab/Shift + Tab). Поэтому вы обычно удваиваете арио-модальное ограничение с ловушкой фокуса, чтобы предотвратить попадание фокуса системы туда, где это не ожидается. Если вы этого не сделаете, вы можете создать проблемы для пользователей программы чтения с экрана (что должна делать программа чтения с экрана, если в данный момент фокус находится на элементе, скрытом от дерева доступности?). Это периодический недосмотр.

Самый безопасный способ сделать фокус-ловушку - это поймать вкладку на последнем разрешенном элементе и shift + tab на первом, и соответственно. вернуть фокус на первый или последний разрешенный элемент. Это гораздо проще, чем установить для всех фокусируемых элементов значение tabindex = -1, а затем вернуться к tabindex = 0, и, как я уже проверял, это работает почти везде.

  • 11 май 2020 2020-05-11 10:17:12