Пишем JavaScript, не забывая о доступности
Советы о том, как улучшить доступность JavaScript-компонентов и дать пользователям более удобные способы взаимодействия с вашим сайтом или веб-приложением.
Перевод статьи Мануэля Матузовича Writing JavaScript with Accessibility in Mind
В моей первой статье «Пишем HTML, не забывая о доступности» я описал причины того, почему меня так заинтересовала тема доступности в вебе. Я также поделился несколькими советами по улучшению разметки с целью повышения доступности вашего сайта. Некоторые из них были довольно базовыми, но от того не менее ценными. Все советы можно свести к двум самым важным неписаным правилам фронтенд-разработки: учите основы и уделяйте достаточно времени написанию HTML. И вы, и ваши пользователи выиграете от чистой и семантичной разметки.
К счастью, HTML — не единственный язык, с помощью которого мы можем создать сайт. Но чем сложнее язык, тем вероятнее что-то пойдет не так. Не смотря на то, что наш код может работать, высока вероятность, что мы забудем о пользователях с устройствами ввода, отличными от мышки или сенсорного экрана. Например, о пользователях клавиатуры или программ чтения с экрана. В этой, второй из четырех, статье о доступности я собрал несколько советов по тому, как писать JavaScript так, чтобы он не влиял негативно на доступность вашего сайта.
JavaScript — не враг
Перед тем, как вы перейдете к советам, я хотел бы пояснить одну очень важную вещь: создание доступного сайта не ставит вас перед выбором использовать ли вообще JavaScript или нет. Доступность — «наука» о том, как сделать контент доступным максимальному количеству пользователей. В их число входят люди со старыми браузерами и компьютерами, медленным интернетом, строгими ограничениями безопасности (например, выключенным JavaScript) и так далее. Опыт пользования сайтом в условиях, когда JavaScript выключен или грузится слишком долго может быть не идеальным, но все же достаточно приемлемым, если сайт отвечает требованиям доступности и удобно спроектирован.
Если JavaScript выполняется, то его можно использовать для улучшения доступности. Сара Суайдан рассказала о своем опыте создания виджета подсказки в статье «Building a fully-accessible help tooltip…is harder than I thought.». Она пишет о том, как «единственно возможное не-JS решение отрицательно сказывается на опыте использования» и почему JavaScript так важен для доступности.
Марко Цихе написал намного больше о JavaScript и доступности в статье «JavaScript is not an enemy of accessibility!». Я настоятельно рекомендую вам прочитать этот текст.
Пора завязывать с вступлениями! Наслаждайтесь!
Виртуозное управление фокусом важно
Очень важно убедится, что по нашему сайту можно ходить при помощи клавиатуры. Многие пользователи полагаются на клавиатуру во время серфинга. Среди них есть люди с моторными дисфункциями, незрячие люди и люди, которые не могут пользоваться мышкой или трекпадом по любым причинам.
Навигация по сайту при помощи клавиатуры означает, что можно передвигаться от одного фиксируемого элемента к другому в том порядке, в котором они расположены в DOM. Обычно используется Tab
для движения вперед или Shift + Tab
для движения назад. Фокусируемыми элементами являются ссылки, кнопки и элементы формы, но не только. Они могут быть активированы при помощи Enter
и, иногда, при помощи пробела. Будучи фокусируемыми и выбираемыми, эти элементы обладают очень полезным дефолтным функционалом. Поэтому так важно использовать правильные семантичные элементы и писать HTML в логическом порядке.
Элементы типа <p>
, <h2>
или <div>
не попадают в фокус по умолчанию. Мы часто используем эти теги для создания компонентов, работающих при помощи JavaScript. Это может усложнить жизнь пользователям с одной лишь клавиатурой.
Делайте нефокусируемые элементы доступными для фокуса
Возможно превратить элемент из недоступного для фокуса в доступный при помощи добавления атрибута tabindex
с целым числом в качестве значения. Если значение будет 0
, то элемент станет доступным для фокуса и выбора с клавиатуры.
Если значением будет отрицательное число, то на элементе можно будет сфокусироваться (например, при помощи JavaScript), но добраться до него с клавиатуры не получится. Вы теоретически можете использовать значения больше 0
, но эту нарушит естественный порядок переключения между элементами, что считается анти-паттерном.
<h2 tabindex=”0">Заголовок, на котором можно сфокусироваться</h2>
Если вы хотите узнать больше о tabindex
, посмотрите эпизод A11ycasts «Controlling focus with tabindex» от Роба Додсона.
Фокусируемся на элементе при помощи JavaScript
Даже если на элементе можно сфокусировать, иногда бывает так, что он находится не в том месте DOM. Чтобы проиллюстрировать эту ситуацию, я создал простое модальное окно на HTML, CSS и JS (демо и сам пен).
Если использовать клавишу Tab
для фокусировки на кнопке и нажать Enter
, на экране появится модальное окно. Если вы снова нажмете Tab
, то фокус переместится на следующую за кнопкой ссылку, которая находится под модальным окном. Пользователь же ожидает, что фокус переместится внутрь всплывающего окна. Но это не так, поскольку элементы попадают в фокус в том порядке, в котором они расположены в DOM, а модальное окно расположено в самом низу документа. Вы можете наблюдать это на скринкасте ниже.
Чтобы исправить эту ситуацию, вы должны сделать модальное окно доступным для фокусировки, а затем сфокусироваться на нем при помощи JavaScript.
HTML
// Добавим tabindex="0"
<div class="modal" id="modal2" tabindex="0">
...
</div>
Javascript
// Используем метод focus() для установки фокуса
function showModal() {
...
var modal = document.getElementById('modal2');
modal.focus();
...
}
Можете посмотреть как это работает в обновленном примере (демо и сам пен). Дойдите Tab
-ом до кнопки, нажмите Enter
и снова нажмите Tab
. Вы видите, что фокус теперь переместился на модальное окно.
Это отлично, но осталась еще пара незакрытых моментов.
Если вы закрываете модальное окно, нажав Esc
, то фокус будет потерян. В идеале, фокус должен вернуться к кнопке, где и был до того, как открылось модальное окно. Для этого вам нужно сохранить последний сфокусированный элемент в переменной.
document.activeElement
содержит текущий элемент в фокусе.
// Переменная для хранения последнего элемента в фокусе
var lastFocusedElement;
function showModal() {
...
// Сохраняем последний элемент в фокусе
lastFocusedElement = document.activeElement;
var modal = document.getElementById(modalID);
modal.focus();
...
}
Теперь, когда у нас есть ссылка на кнопку, мы можем снова сфокусироваться на нее, когда модальное окно закроется.
function removeModal() {
...
// Возвращаем фокус на последний элемент в фокусе
lastFocusedElement.focus();
...
}
Я обновил код в другом пене (демо и сам пен). Доступность сейчас намного лучше, но есть еще возможности для улучшения.
При открытии окна рекомендуется держать фокус в его пределах. В данный момент пользователь все еще может выйти за его пределы, нажимая Tab
.
Я не буду вдаваться в подробности касаемо реализации, но вы можете посмотреть код с так называемой клавиатурной ловушкой (демо и сам пен). Фокус будет оставаться в пределах модального окна до тех пор, пока оно открыто.
Если вы сравните первый и последний пены, вы увидите, что количество кода особо не увеличилось. Это решение, вероятно, не идеально, но в конечном итоге интерфейсом стало удобнее пользоваться.
Есть еще один пример доступного модального окна и отличная статья под названием «Using tabindex» от команды из Google.
Если вы хотите узнать больше о тестировании при помощи клавиатуры, то посетите сайт WebAIM. У них есть список «стандартных интерактивных действий, стандартных нажатий клавиш и дополнительная информация о моментах, которые следует учитывать при тестировании». Другие примеры хорошей работы с фокусом можно найти в видео «Focus management using CSS, HTML, and JavaScript» от Марси Саттон (egghead.io) или эпизод A11ycast «What is Focus?“» от Роба Додсона.
Если нужна кнопка, используйте элемент <button>
Я уже писал о кнопках в первой статье, но, видимо, многие люди используют другие элементы взамен кнопок. Так что, я думаю, не повредит написать еще раз об этой теме.
Я создал пен (режим отладки / пен с кодом) для иллюстрации нескольких случаев использования span
или div
вместо button
или input
. Если вы начнете перемещаться по странице при помощи Tab
, то обратите внимание, что можно сфокусироваться на первой кнопке, но не на второй. И все это из-за того, что первая кнопка это button
, а вторая — div
. Вы можете добавить кнопке-div
атрибут tabindex="0"
и тем самым позволить кнопке попадать в фокус. Именно поэтому мы можем сфокусироваться на третьей и четвертой кнопках, хотя они тоже являются элементами div
.
// Кнопка попадает в фокус
<button class="btn">I'm a button</button>// div не попадает в фокус
<div class="btn">I'm a div</div>// Все еще просто div, но попадает в фокус
<div class="btn" tabindex="0">I'm a div</div>// Назначена роль кнопки и доступна для фокуса
<div class="btn" tabindex="0" role="button">I'm a div</div>
div
-кнопка попадает в фокус, но все еще ведет себя как div
, даже если вы добавите атрибут role
со значением button
. Чтобы показать это, я добавил простой обработчик клика на все элементы с классом .btn
(пен). Если вы кликните мышкой на кнопки, то появится поп-ап. Но если вы попробуете добиться того же результата, используя клавиши Enter
или пробел, то только первая кнопка вызовет событие. Вам нужно будет самостоятельно добавить обработчики событий на div
-кнопки чтобы их поведение полностью соответствовало стандартному поведению кнопки. Слишком много лишних действий, вам не кажется? Вот почему вам следует использовать <button>
если нужна кнопка.
Посмотрите эпизод шоу A11ycast «Just use button» от Роба Додсона или почитайте статью «Links, Buttons, Submits, and Divs, Oh Hell» от Адриана Розелли.
Пользователи экранных читалок должны быть в курсе когда контент меняется динамически
Обычно экранные читалки произносят тот контент, который находится в сфокусированном элементе или пользователь использует определенные клавиши навигации. Если контент подгружается динамически и вставляется в DOM, то только зрячий человек будет в курсе этого. ARIA Live Regions (далее живые регионы) предоставляют несколько опций для работы с этой задачей. Я покажу пару примеров.
Предположим, у вас есть страница настроек профиля, на которой вы можете редактировать персональные данные и сохранять их. При нажатии на кнопку изменения сохраняются без перезагрузки страницы. Предупреждения информируют пользователя, было ли сохранение успешным или нет. Предупреждение может появится сразу же или через какое-то время. Я записал небольшое видео чтобы показать вам этот пример.
Вы можете видеть, что сохранение прошло успешно. Но услышать этого вы не можете. Пользователи скринридеров не заметят изменений. К счастью, есть простое решение этой проблемы. Если элементу добавить атрибут role
со значением status
или alert
, то программа чтения будет отслеживать изменение контента в этом элементе.
<div class="message" role="status">Изменения сохранены!</div>
Если текст сообщения изменится, то программа заметит это и прочитает новый контент. Вы можете увидеть и услышать это в видео или посмотреть на код в пене.
Будьте вежливы по отношению к пользователям
Разница между status
и alert
в том, что последний будет прерывать скринридер, если он в этот момент произносит какой-то другой контент. status
дождется, когда читалка закончит читать фрагмент.
Существует другой атрибут, называемый aria-live
. У него есть три значения: off
, polite
или assertive
. off
является значением по умолчанию. aria-live="polite"
эквивалентен role="status"
и aria-live="assertive"
соответствует role="alert"
. В некоторых «общеизвестных определенных случаях предпочтительнее использовать конкретно “роль живого региона”». Также, если браузер не поддерживает role
, вы можете попробовать использовать оба атрибута. Леони Уотсон поделилась результатами тестов в статье «Screen reader support for ARIA live regions».
<div role="alert" aria-live="assertive"></div>
Иногда имеет смысл проговорить не только изменившийся контент
По умолчанию экранные читалки проговаривают только изменившийся контент, даже если в пределах одного живого региона есть другой контент. Но иногда имеет смысл проговорить весь текст целиком.
Есть способ изменить стандартное поведение при помощи атрибута aria-atomic
. Если вы установите значение true
, вспомогательные технологии произнесут все содержимое элементы.
Пол Дж. Адам сравнил различные настройки живых регионов и собрал демо с кейсами aria-atomic
. Он также протестировал свое демо при помощи VoiceOver в iOS 8.1 и записал результат, так что вы можете видеть работу атрибута в действии. Я предлагаю вам посмотреть запись (VoiceOver iOS 8.1 Speaking Characters Remaining aria-atomic & aria-relevant on aria-live regions), если вы хотите понять, в каких случаях лучше использовать aria-atomic
.
Учитывайте следующие моменты
- Живые регионы не перемещают фокус, они только вызывают повторное прочтение текста
- Используйте
alert
только при критических изменениях.status
больше подойдет в большинстве ситуаций, поскольку он более учтивый - Избегайте создания автоматически скрывающихся уведомлений, поскольку они могут пропадать слишком быстро
- Во время своих тестов я использовал VoiceOver. Скрытие уведомлений через CSS или их динамическое создание вообще не срабатывали. Убедитесь, что вы протестировали свои живые регионы в разных браузерах и на разном ПО.
Конечно же существует эпизод A11ycast «Alerts!» с более детальными примерами. Хейдон Пикеринг создал другой пример живых регионов, который вы можете найти в его коллекции примеров работы с ARIA.
Вам не нужно гадать, какие шаблоны взаимодействия должен поддерживать ваш виджет
Часто сложно составить полный список возможностей, которые должен покрывать виджет с точки зрения навигации и доступности. К счастью, существует ресурс под названием WAI-ARIA Authoring Practices 1.1, который может помочь нам в этом. «WAI-ARIA Authoring Practices это гайд по использованию WAI-ARIA для создания доступных интернет-приложений. В нем описаны предпочтительные WIA-ARIA шаблоны и дается представление о концепциях, стоящих за ними.»
У них есть гайды по созданию аккордеонов, слайдеров, вкладок и т. д.
Доступные JavaScript-компоненты
Существуют также крупные ресурсы с доступными компонентами на JavaScript.
- Practical ARIA examples
- Modaal — Modaal is a WCAG 2.0 Level AA accessible modal window plugin.
- Frend — A collection of accessible, modern front-end components.
- The A11Y Project patterns
Если вы знаете другие ресурсы, то делитесь ими в комментариях.
Резюмируя
Воспользуйтесь преимуществами JavaScript, чтобы улучшить доступность вашего сайта. Позаботьтесь об управлении фокусом, узнайте новое об общих шаблонах использования и учитывайте пользователей экранных читалок, когда манипулируете DOM. Прежде всего, не забывайте, для кого вы делаете сайты и получайте удовольствие в процессе разработки.
За рамками
На сегодня это все. Я надеюсь, эти советы помогут вам писать более доступный JavaScript. Огромное спасибо Хейдону Пикерингу, поскольку его книга «Inclusive Front-End Design Patterns» заложила фундамент для большей части из того, что вы только что прочитали. Если вам интересно узнать больше о доступности и инклюзивном дизайне, то я крайне рекомендую вам эту книгу.
Больше фишек доступности
Эта статья является второй в серии из четырех. Остальные находятся в разработке и вскоре будут выпущены.
- Пишем HTML, не забывая о доступности
- Пишем JavaScript, не забывая о доступности
- Пишем CSS, не забывая о доступности
- Дальше: Learn how to design and develop with accessibility in mind
Благодарю вас за чтение и, пожалуйста, не забудьте поаплодировать и поделиться этой статьей, если вам понравилось.
Особая благодарность Адриану Роселли за то, что он помог мне с этой статьей, и Еве за корректуру моего текста.
Пока я работаю над следующим постом, вы можете почитать другие статьи, которые я написал:
Ресурсы
- Book: Inclusive Front-End Design Patterns by Heydon Pickering.
- WebAIM Keyboard Accessibility
- Focusable Elements — Browser Compatibility Table
- Using tabindex
- WebAIM Keyboard Testing
- HTMLElement.focus()
- MDN: tabindex
- The Incredible Accessible Modal Window
- Keyboard-navigable JavaScript widgets
- Focus management using CSS, HTML, and JavaScript
- Alerts! — A11ycasts #10
- What is Focus? — A11ycasts #03
- Controlling focus with tabindex — A11ycasts #04
- Just use button — A11ycasts #05
- Don’t Use Tabindex Greater than 0
- Links, Buttons, Submits, and Divs, Oh Hell
- WAI-ARIA aria-atomic