[Из песочницы] Решение проблемы обеспечения доступности модального окна для людей с ограниченными возможностями02.11.2020 07:17
Всем привет!
В этой статье я бы хотел рассказать как реализовать доступное модальное окно, без использование аттрибута «aria-modal».
Немного теории!
«aria-modal» — это аттрибут использующийся для того, что бы сообщить вспомогательным технологиям (таким, как программы чтения с экрана), что веб-контент под текущим диалоговым окном недоступны для взаимодействия (инертен). Другими словами, ни один елемент под модальным окном не должен получить фокус при клике, навигации с использованием «TAB/SHIFT+TAB» или при свайпе на сенсерных устройствах.
Но почему мы не можем использовать «aria-modal» для модального окна?
Существуют несколько причин:
просто не поддерживаются программами чтения с экрана
игнорируется псевдо классами »: before/: after»
Перейдем к реализации.
Реализация
Для того что бы начать разработку, нам необходимо выделить свойста, которыми должно обладать доступное модальное окно:
все интрерактивные элементы, вне модального окна, должны быть заблокированы для манипуляции пользователем: клика, установки фокуса и так далее…
навигация должны быть доступна только по системным компонентам браузера и по контенту самого модального окана (весь контент вне модального окна должен быть проигнорирован)
Заготовка
Воспользуемся заготовкой, чтобы не тратить время на пошаговое описание создания модального окна.
HTML:
Document
Custom button
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt maxime tenetur sint porro tempore aperiam! Eaque tempore repudiandae culpa omnis placeat, fugit nostrum quisquam in ipsa odit accusamus illum velit?
Modal window
Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita, doloribus.
Если открыть страницу и попробывать перейти на элементы, находящиеся за модальным окном, используя клавиши «TAB/SHIFT+TAB», то эти элементы получают фокус, как показано на прикрепленной картинке.
Чтобы решить эту проблему, нам необходимо присвоить всем интерактивным элементам атрибут 'tabindex' со значением минус единица.
1. Для дальнейшей работы создаем класс «modalWindow» cо следующими свойствами и методами:
doc — документ страницы. в котором строим модальное окно
modal — контейнер модального окна
interactiveElementsList — массив интерактивных элементов
blockElementsList- массив блочных элементов страницы
constructor — конструктор класса
create — метод используемый для создания модального окна
remove — метод используемы для удаления модального окна
4. В методе 'create' выбираем все элементы подходящие по нашим селекторам и выставляем всем 'tabindex=-1' (игнорируем элементы, которые уже имеют данное значение)
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
Похожая проблема возникает когда мы используем специальные клавиши или жесты (в мобильных программах) для навигации, в этом случае мы можем навигироваться не только по интерактивным элементам, но и по текстовым. Чтобы исправить этот случай, нам нужно добавить
5. Здесь нам не нужно создавать массив для хранения селекторов, мы просто возьмем все дочерние элементы узла 'body'
let children = this.doc.body.children;
6. Четвертый шаг аналогичен шагу 2, только с использованием 'aria-hidden'
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
Завершенный метод «create»:
create() {
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
let children = this.doc.body.children;
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
}
7. На шестом шаге реализуем метод обратный 'create':
remove() {
let element;
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('tabindex', '0');
}
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('aria-gidden', 'false');
}
}
8. Что бы это все заработало нам нужно создать экземпляр класса «modalWindow» и вызвать методы «create» и «remove»: