Управ­ле­ние фо­ку­сом и ат­ри­бут inert

Перевод «Focus management and inert»

Эрик Бейли

Перевод Михаил Данюшин

Редактура Никита Дубко Татьяна Фокина Вадим Макеев Василий Дудин

Множество вспомогательных технологий используют навигацию с клавиатуры для помощи в восприятии и взаимодействии с контентом. Один из способов подобной навигации — клавиша Tab. Возможно, вы знакомы с ним, если используете Tab для быстрого перемещения между полями формы, не дотягиваясь до мышки или трекпада.

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

Список интерактивных элементов, по которым можно пройтись клавишей Tab:

  • Ссылки с заполненным атрибутом href;
  • <button>;
  • <input> и <textarea> с сопутствующим им <label>;
  • <select>;
  • <details>;
  • <audio> и <video> при наличии контролов;
  • <object>, в зависимости от того, как он используется;
  • любой элемент с overflow: scroll в Firefox;
  • любой элемент с атрибутом contenteditable;
  • любой элемент с установленным атрибутом tabindex (о нём чуть позже).

Интерактивный элемент получает состояние фокуса, когда:

  • На него переходят с помощью клавиши Tab,
  • Он является ссылкой и на него кликают,
  • Фокус программно задан с помощью element.focus() в JavaScript.

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

Фокус следует по домашней странице. Начиная с логотипа, затем к товарам, услугам, вакансиям, блогу, контактам и останавливается на кнопке «Learn more».

Управление фокусом#

Управлять фокусом — значит определять, какие элементы могут его получать, а какие нет. Это один из самых сложных аспектов при разработке веб-интерфейсов, но он важен для доступности сайтов и приложений.

Полезные практики управления фокусом#

В 99% случаев вам стоит оставить порядок фокуса в покое. Не устану это повторять.

Состояние фокуса будет работать без дополнительных усилий, если вы используете <button> для кнопок, <a> для ссылок, <input> для полей форм и т. д.

В редких случаях вам может понадобиться добавить фокус к элементам, к которым обычно событие focus не применимо. Вот некоторые рекомендации относительно того, как реализовать это доступно и интуитивно понятно для пользователя.

✅ Следует: узнать побольше про атрибут tabindex#

tabindex делает элементы фокусируемыми. В качестве значения он принимает число, в зависимости от которого меняется поведение фокуса.

❌ Не следует: устанавливать tabindex="0" там, где это не надо#

Нет необходимости устанавливать tabindex для интерактивных элементов, которые могут получать фокус с клавиатуры (например, <button>). Кроме того, вам не нужно прописывать tabindex неинтерактивным элементам, чтобы их могли прочесть вспомогательные устройства (на самом деле, отсутствие роли и доступного имени является ошибкой с точки зрения WCAG). На самом деле, это даже усложняет навигацию для тех, кто использует вспомогательные устройства. У таких пользователей уже есть другие, ожидаемые ими способы чтения контента.

✅ Следует: устанавливать tabindex="-1" для фокуса с помощью JavaScript#

Атрибут tabindex="-1" используется для создания доступных интерактивных виджетов посредством JS. Прописав tabindex="-1", вы сделаете элемент фокусируемым для JS, а также по клику или тапу, но недоступным через клавишу Tab.

❌ Не следует: использовать положительное значение tabindex#

Это антипаттерн. Установив положительное значение tabindex, вы переопределите ожидаемый порядок элементов для фокуса через Tab и запутаете пользователя. Сделать так один раз — уже плохо, несколько — полный кошмар. Серьёзно, не надо так.

❌ Не следует: создавать собственный порядок фокусировки#

Пройтись по интерактивным элементам можно только в том порядке, в котором они следуют друг за другом. Не следует создавать множество tabindex со значением, которое увеличивается для каждого последующего элемента, в соответствии с вашим представлением о том, как пользователь должен читать ваш сайт. Позвольте DOM сделать это за вас.

Ловушка фокуса#

Иногда есть необходимость запретить состояние фокуса. Хороший пример запрета — это ловушка фокуса, которая временно ограничивает событие фокуса у родительского элемента и у его дочерних элементов.

Ловушку фокуса не стоит путать с ловушкой клавиатуры. Ловушки клавиатуры — это ситуации, когда невозможно закрыть виджет или перейти к другому компоненту из-за вложенного цикла плохо прописанной логики.

Практический пример того, как вы могли бы использовать ловушку фокуса — это модальное окно:

Фокус проходит по странице и открывает модальное окно, чтобы продемонстрировать отмену фокуса. Далее фокус двигается в рамках контента модального окна, на кнопку «Play», кнопку «Cancel», кнопку «Purchase» и кнопку закрытия (всё это время фокус на странице заблокирован). После закрытия модального окна он возвращается к исходному положению на странице до его открытия.

Почему это важно?#

Удержание фокуса в пределах модального окна помогает понять, что является его контентом, а что нет. Это аналогично тому, как зрячий человек может видеть, как окно всплывает поверх контента сайта или приложения. Это важная информация, если:

  • У вас плохое зрение или слепота и вы полагаетесь на скринридер, чтобы узнать об изменениях после взаимодействия со страницей;
  • У вас плохое зрение и интерфейс увеличен с помощью экранной лупы, при которой фокусировка за пределами модального окна может сбить с толку и дезориентировать;
  • Вы перемещаетесь исключительно с помощью клавиатуры и, в случае закрытия модального окна, можете потеряться на странице при попытке вернуться обратно к нему.

Как это сделать?#

Надёжно управлять фокусом — дело сложное. Нужно прибегнуть к JavaScript, чтобы:

  1. Определить родительский блок для всех фокусируемых элементов на странице;
  2. Определить границы содержимого ловушки фокуса (например, модального окна), включая первый и последний фокусируемый элемент;
  3. Убрать как интерактивность, так и видимость всего, что может иметь фокус и находится вне рамок содержимого ловушки фокуса;
  4. Переместить фокус на содержимое ловушки фокуса;
  5. Обрабатывать события, сигнализирующие об уходе с выделенной области (сохранение, отмена, нажатие Esc и так далее);
  6. Выйти из содержимого ловушки фокуса, когда сработает нужное событие;
  7. Вернуть раннее отменённую интерактивность;
  8. Переместить фокус обратно на интерактивный элемент, который вызвал ловушку фокуса.

Зачем нам это?#

Не стану врать: все эти действия отнимают много времени. Но всё же, управление фокусом и удобный порядок фокусировки являются частью WCAG (руководства по обеспечению доступности веб-контента). Тема достаточна важна, чтобы считать её частью международного правового стандарта о юзабилити.

Видимость#

Существует небольшой трюк, с помощью которого можно легко ограничить видимость и интерактивность элемента.

У скринридеров есть режим взаимодействия, который позволяет им проходить по странице или просматривать её с помощью виртуального курсора. А ещё виртуальный курсор позволяет пользователю скринридера обнаруживать неинтерактивные части страницы (заголовки, списки и т. д.). В отличие от использования Tab, виртуальный курсор доступен только пользователям скринридера.

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

Видимость может быть заблокирована с помощью правильного применения атрибута aria-hidden="true". А вот с интерактивностью есть один тонкий нюанс.

Знакомство с inert#

Атрибут inert — глобальный атрибут в HTML, позволяющий легко убирать и восстанавливать видимость интерактивных элементов, а также их возможность получать состояние фокуса. Вот пример:

<body>
    <div
        aria-labelledby="modal-title"
        class="c-modal"
        id="modal"
        role="dialog"
        tabindex="-1">
        <div role="document">
            <h2 id="modal-title">Save changes?</h2>
            <p>The changes you have made will be lost if you do not save them.<p>
            <button type="button">Save</button>
            <button type="button">Discard</button>
        </div>
    </div>
    <main inert>
        <!-- ... -->
    </main>
</body>

Я намеренно избегаю использования <dialog> для модального окна из-за многочисленных проблем с поддержкой.

Атрибут inert задан элементу <main>, следующему сразу после модального окна сохранения изменений. Теперь всё содержимое <main> не может попасть в фокус и по нему нельзя кликнуть.

Фокус ограничен рамками модального окна. Когда мы закрываем его, то убираем inert из <main>. Этот способ управления ловушкой фокуса проще, чем другие методы.

Помните: событие закрытия модального окна может быть вызвано не только нажатием на кнопки внутри нашего модального окна из примера, но и при нажатии на Esc. А ещё некоторые модальные окна могут быть закрыты по клику на оверлей.

Поддержка inert#

Все последние версии Edge, Chrome и Opera поддерживают inert, если включены экспериментальные функции веб-платформ. Также скоро появится его поддержка в Firefox! Единственное исключение — Safari, как мобильный, так и десктопный.

Мне бы очень хотелось, чтобы Apple реализовала встроенную поддержку inert. Хоть полифил и существует, но у него серьёзные проблемы с поддержкой скринридерами. Нехорошо!

Вдобавок хочется обратить внимание на пометку в README полифила:

Полифил обойдётся вам дорого с точки зрения производительности, в отличие от нативного inert, т. к. он требует изрядного количества перемещений по DOM.

Перемещение по DOM подразумевает, что JavaScript в полифиле требует высокой вычислительной мощности и, следовательно, в конечном итоге замедлит использование.

Для устройств с низким энергопотреблением, таких как бюджетные Android-смартфоны и устаревшие ноутбуки, а также тех, что выполняют сложные задачи (например, запуск нескольких приложений сразу), это может привести к зависанию или сбоям. Нативная браузерная поддержка делает такие процессы менее затратными в этом плане, так как у браузера есть доступ ко всем частям DOM, в отличие от JS.

Safari#

Лично я разочарован тем, что Apple не поддерживает inert. Хотя понимаю, что добавление новых функций в браузер — невероятно сложная и трудная работа. inert кажется функцией, которую Apple стоило бы начать поддерживать гораздо раньше.

macOS и iOS всегда имели хорошую поддержку доступности, а поддержка вспомогательных технологий — важная часть их маркетинговой кампании. Поддержка inert представляется как естественное продолжение миссии Apple ввиду того, что сама эта функция смогла бы облегчить разработку доступных веб-интерфейсов в разы.

К сожалению, Apple держит в тайне, когда появится поддержка этого атрибута. Поэтому поддержка inert — всё ещё открытый вопрос.

Igalia#

Igalia — компания, работающая над функциями браузеров. Сейчас они проводят эксперимент, в котором каждый может проголосовать за те возможности браузеров, которые ему хотелось бы видеть. Конечно, моя статья совсем не про это, но вы можете узнать больше в Smashing Magazine.

Одно из предложений Igalia — это добавление поддержки inert в WebKit. Если вы искали возможность принять участие в улучшении доступности веба, но не знали с чего начать, я могу вам её предоставить. 5 $, 10 $, 25 $ — совсем необязательно жертвовать большие суммы, даже маленький вклад ценен.

Пожертвовать

Итог#

Управление фокусом требует некоторых навыков и осторожности, но это того стоит. Атрибут inert значительно облегчит эту задачу.

Такие техники, как inert, являются примером одной из самых сильных сторон веба: способности кодифицировать неожиданное поведение в нечто простое и эффективное.

Что почитать#

Спасибо Адриану Розелли и Саре Хайли за их отзывы.