Русский
Русский
English
Статистика
Реклама

Из песочницы Решение проблемы обеспечения доступности модального окна для людей с ограниченными возможностями

Всем привет!

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

Немного теории!


aria-modal это аттрибут использующийся для того, что бы сообщить вспомогательным технологиям (таким, как программы чтения с экрана), что веб-контент под текущим диалоговым окном недоступны для взаимодействия (инертен). Другими словами, ни один елемент под модальным окном не должен получить фокус при клике, навигации с использованием TAB/SHIFT+TAB или при свайпе на сенсерных устройствах.

Но почему мы не можем использовать aria-modal для модального окна?

Существуют несколько причин:

  • просто не поддерживаются программами чтения с экрана
  • игнорируется псевдо классами ":before/:after"

Перейдем к реализации.

Реализация


Для того что бы начать разработку, нам необходимо выделить свойста, которыми должно обладать доступное модальное окно:

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

Заготовка


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

HTML:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    <div>        <button type="button" id="infoBtn" class="btn"> Standart button </button>        <button type="button" id="openBtn"> Open modal window</button>        <div role="button" tabindex="0" id="infoBtn" class="btn"> Custom button </button>    </div>    <div>        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?    </div>    <div id="modalWindow" class="modal">        <div>            <button type="button" id="closeBtn" class="btn-close">Close</button>            <h2>Modal window</h2>            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita, doloribus.</p>        </div>    </div></body></html>

Стили:

    .modal {        position: fixed;        font-family: Arial, Helvetica, sans-serif;        top: 0;        right: 0;        bottom: 0;        left: 0;        background: rgba(0,0,0,0.8);        z-index: 99999;        transition: opacity 400ms ease-in;        display: none;        pointer-events: none;    }        .active{        display: block;        pointer-events: auto;    }    .modal > div {        width: 400px;        position: relative;        margin: 10% auto;        padding: 5px 20px 13px 20px;        border-radius: 10px;        background: #fff;    }    .btn-close {        padding: 5px;        position: absolute;        right: 10px;        border: none;        background: red;        color: #fff;        box-shadow: 0 0 10px rgba(0,0,0,0.5);    }    .btn {        display: inline-block;        border: 1px solid #222;        padding: 3px 10px;        background: #ddd;        box-sizing: border-box;    }

JS:

    let modaWindow = document.getElementById('modalWindow');    document.getElementById('openBtn').addEventListener('click', function() {        modaWindow.classList.add('active');    });    document.getElementById('closeBtn').addEventListener('click', function() {        modaWindow.classList.remove('active');    });

Если открыть страницу и попробывать перейти на элементы, находящиеся за модальным окном, используя клавиши TAB/SHIFT+TAB, то эти элементы получают фокус, как показано на прикрепленной картинке.

image

Чтобы решить эту проблему, нам необходимо присвоить всем интерактивным элементам атрибут 'tabindex' со значением минус единица.

1. Для дальнейшей работы создаем класс modalWindow cо следующими свойствами и методами:

  • doc документ страницы. в котором строим модальное окно
  • modal контейнер модального окна
  • interactiveElementsList массив интерактивных элементов
  • blockElementsList- массив блочных элементов страницы
  • constructor конструктор класса
  • create метод используемый для создания модального окна
  • remove метод используемы для удаления модального окна

2. Реализуем конструктор:

constructor(doc, modal) {    this.doc = doc;    this.modal = modal;    this.interactiveElementsList = [];    this.blockElementsList = [];}

interactiveElementsList и blockElementsList нужны чтобы содержать элементы страницы, которые были изменены в момент создания модального окна.

3. Создаем константу, в которой будем хранить список все элементов, которые могут иметь фокус:

const INTERECTIVE_SELECTORS = ['a', 'button', 'input', 'textarea', '[tabindex]'];

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:

    let modaWindow = document.getElementById('modalWindow');    const modal = new modalWindow(document, modaWindow);    document.getElementById('openBtn').addEventListener('click', function() {        modaWindow.classList.add('active');       // modal.create();    });    document.getElementById('closeBtn').addEventListener('click', function() {        modaWindow.classList.remove('active');       // modal.remove();    });

Полный код класса:

class modalWindow{    constructor(doc, modal) {        this.doc = doc;        this.modal = modal;        this.interactiveElementsList = [];        this.blockElementsList = [];    }    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);                }            }        }    }    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');        }    }

P.S.


Если на мобильных устройствах не решена проблемы с навигацией по текстовым элементам, то можно использовать следующую выборку:

  const BLOCKS_SELECTORS = ['div', 'header', 'main', 'section', 'footer'];  let children = this.doc.querySelectorAll(BLOCKS_SELECTORS .toString());

Ссылки на полезные ресурсы


Источник: habr.com
К списку статей
Опубликовано: 01.11.2020 18:17:43
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Html

Accessibility

Javascript hacks

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru