Прокачиваем вёрстку ARIA-атрибутами. Атрибут aria-hidden

от автора

Хабр, я рассказал вам, как создавать подсказки с помощью атрибута aria-label. Это отличный шаг к классному опыту для пользователей скринридера. А сегодня сделаем второй — мы научимся правильно скрывать элементы. Встречайте, атрибут aria-hidden.

▍ Что это такое?

О проблеме доступности цифровых интерфейсов задумались относительно недавно. Лично я изучаю её с 2016 года. А интерфейсы же проектировали ещё в 90-х годах. По этой причине подавляющее большинство, если не все, спроектированы для людей, видящих их. Графические элементы, механики взаимодействия, элементы управления — всё заточено под человека, который может увидеть их.

Небольшая, но значимая часть этого опыта в принципе не может подходить для пользователя скринридера. Тут возникает потребность скрыть то, что бесполезно, и заменить полезным и удобным. Здесь вотчина атрибута aria-hidden.

Он полностью скрывает элемент от скринридера. Чем-то этим напоминает атрибут hidden. Только есть разница. Когда элемент скрывается при помощи атрибута hidden, он не доступен скринридерам и его нельзя увидеть. При использовании атрибута aria-hidden он продолжает отображаться визуально.

Для скрытия элемента нужно использовать значение true, добавив атрибут с ним к элементу. Например, я сделал это для элемента <span>.

<body>   <span aria-hidden="true">Этот текст не доступен для скринридера. Он не будет озвучен</span> </body> 

Теперь о тексте в элементе <span> знают только зрячие пользователи. Скринридером его прочитать нельзя.

▍ Используем на практике

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

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

Атрибут aria-hidden="true" упрощает взаимодействие пользователям скринридера. Часто нужно скрывать декоративные элементы. Звучит вроде просто, но всё не так однозначно. Для примера я взял заголовок видео одного популярного Youtube-канала.

<body>   <span>Дзюба х Слуцкий х Роман Евгеньев</span> </body> 

Есть предположения, где здесь скрыта сложность? Буква «х». Авторы видео используют его в качестве декоративного разделителя. Для зрячего пользователя в этом нет проблем. Он пропустит символ и прочитает «Дзюба», «Слуцкий» и «Роман Евгеньев». Пользователи скринридеров не могут так. Они услышат текст полностью: «Дзюба ха Слуцкий ха Роман Евгеньев». Вот и думай причём тут «ха».

Исправить эту проблему просто. Нужно предоставить пользователям скринридеров такой же текст, какой видят зрячие. Они должны услышать: «Дзюба Слуцкий Роман Евгеньев». Для достижения этой цели скроем декоративные разделители с помощью aria-hidden="true".

<body>   <span>Дзюба <span aria-hidden="true">х</span> Слуцкий <span aria-hidden="true">х</span> Роман Евгеньев</span> </body> 

Следующим примером полезного использования атрибута aria-hidden будут SVG-иконки внутри интерактивных элементов. Часто внутри элемента <svg> могут быть элементы <title>, <text>, <image> и <desc>. Для примера я добавил элемент <title> внутрь элемента <svg>.

<body>   <button type="button">     <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">       <title>Стрелка</title>       <polyline stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="19 11 13 5 7 11"></polyline>       <path d="M13 5 L13 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>     </svg>     <span>Перейти к началу страницы</span>   </button> </body> 

Здесь скринридеры найдут иконку, а ещё в придачу текст «Стрелка». Конечно, всё расскажут пользователю. И такая же история будет с элементами <text>, <image> и <desc>.

Чтобы не спамить нашим пользователям, лучше скрыть декоративную иконку. Первое, что можно сделать, это удалить элемент <title>.

<body>   <button type="button">     <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">       <polyline stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="19 11 13 5 7 11"></polyline>       <path d="M13 5 L13 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>     </svg>     <span>Перейти к началу страницы</span>   </button> </body> 

Это поможет. Но часто бывает так, что в будущем элемент снова добавляют. Поэтому надёжнее скрыть железобетонно. Конечно, сделает это атрибут aria-hidden. Его нужно добавить к элементу <svg>.

<body>   <button type="button">     <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">       <polyline stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="19 11 13 5 7 11"></polyline>       <path d="M13 5 L13 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>     </svg>     <span>Перейти к началу страницы</span>   </button> </body> 

Третьим примером полезного применения атрибута будет создание CSS-анимации. Бывает так, что в ней используется текст. Такой случай был у меня. Мне нужно было создать анимацию для лоадера, в котором анимировалось слово «Loading». Разметка была такой:

<body>   <span class="cp-preloader" data-preloader-text="Loading...">Loading...</span> </body> 

Для создания эффекта я использовал свойство content. В нём я дублировал текст, чтобы он эффектно появлялся.

.cp-preloader {   position: relative;   color: rgba(0, 0, 0, 0.2); }  .cp-preloader::before {   content: attr(data-preloader-text);   color: #222;   position: absolute;   inset: 0; }  @media (prefers-reduced-motion: no-preference) {    .cp-preloader::before {     animation: cp-preloader34 6s ease-in-out infinite both;   }    @keyframes cp-preloader34 {     0%, 10%, 90%, 100% {       clip-path: circle(0 at 50% 50%);     }     40%, 60% {       clip-path: circle(50vmax at 50% 50%);     }   } } 

Тогда я не знал, что скринридеры озвучивают текст из свойства content… В итоге они дважды говорили «Loading».

Я начал думать, как исправить. Ответ очевиден. Нужно вставить текст в HTML и сделать его недоступным для скринридеров. Задачка для атрибута aria-hidden.

<body>   <span class="cp-preloader">     <span class="cp-preloader__text" aria-hidden="true">Loading...</span>     Loading...   </span> </body> 

.cp-preloader {   position: relative;   color: rgba(0, 0, 0, 0.2); }  .cp-preloader__text {   color: #222;   position: absolute;   inset: 0; }  @media (prefers-reduced-motion: no-preference) {    .cp-preloader__text {     animation: cp-preloader34 6s ease-in-out infinite both;   }    @keyframes cp-preloader34 {     0%, 10%, 90%, 100% {       clip-path: circle(0 at 50% 50%);     }     40%, 60% {       clip-path: circle(50vmax at 50% 50%);     }   } } 

Так я сохранил анимацию и не навредил пользователям скринридера.

▍ Особенности использования

Мы уже узнали про значение true. Кроме него есть ещё два: false и undefined. Рассмотрим все их более подробно. У них куча нюансов!

Начнём с того, что добавим для элемента <span> только сам атрибут aria-hidden.

<body>   <span aria-hidden>Этот текст доступен для скринридера?</span> </body> 

В этом случае браузер вместе со скринридером понимают значение атрибута как undefined. Итоговое решение о доступности элемента будет определено, опираясь на его стили. В теории в большинстве случаев всё будет хорошо.

Я провёл тест в скринридере NVDA и JAWS в трёх браузерах. Google Chrome, Firefox и Edge. Результаты одинаковые. Во всех браузерах текст озвучивается. Правда, есть несколько случаев, когда текст не будет доступен.

Первый — это использование CSS-свойства display со значением none.

<body>   <span aria-hidden class="non-accessible-text">Этот текст недоступен для скринридера. Он не будет озвучен</span> </body> 

.non-accessible-text {   display: none; } 

Второй случай является следствием первого. Атрибут hidden устанавливает display: none для элемента.

<body>   <span aria-hidden hidden>Этот текст недоступен для скринридера. Он не будет озвучен</span> </body> 

Третий — это CSS-свойство visibility со значением hidden.

<body>   <span aria-hidden class="non-accessible-text">Этот текст недоступен для скринридера. Он не будет озвучен</span> </body> 

.non-accessible-text {   visibility: hidden; } 

Значение false. Оно сообщает, что элемент доступен для скринридера. На практике оно нужно очень редко. Лично я ни разу его не использовал. Нет смысла делать то, что уже сделано без моего участия.

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

И наконец значение true. Да, оно полностью скрывает элемент вместе с дочерними элементами, если они есть.

<body>   <span aria-hidden="true">Этот текст недоступен для скринридера. Он не будет озвучен</span>    <div aria-hidden="true">     <span>Родительский элемент и этот текст тоже недоступны для скринридера</span>   </div> </body> 

Правда здесь нас ждёт подвох. К сожалению, aria-hidden="true" сработает не для каждого элемента. Для интерактивных есть особенности. Попробуем добавить атрибут для элемента <button>.

<body>   <!-- Элемент button будет скрыт не всегда -->   <button aria-hidden="true" type="button">Перейти</button> </body> 

Если пользователь скринридера переключился на элемент <button> при помощи клавиш стрелок ( или ), то он будет недоступен. Но! Если это произойдёт при помощи клавиши Tab, то кнопка станет доступной. Тут начинается полная вакханалия.

Скринридеры ведут себя по-разному. JAWS в Google Chrome, Firefox и Edge будет озвучивать либо предыдущий, либо следующий элемент. Я не понял, от чего это зависит. NVDA в тех же браузерах скажет «Пусто». VoiceOver в Google Chrome и Safari вообще произнесут текст кнопки, как будто её не скрывали. А в Firefox она будет скрыта. В общем полный капут.

Такие же проблемы нас ждут, если добавить aria-hidden="true" к родительскому элементу, который содержит интерактивный элемент.

<body>   <!-- Элемент button будет скрыт не всегда -->   <div aria-hidden="true">     <button type="button">Перейти</button>   </div> </body> 

По этой причине строго нельзя использовать aria-hidden="true" для элементов, содержащих интерактивные элементы.

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

<body>   <!-- Элемент button будет скрыт всегда -->   <button aria-hidden="true" tabindex="-1" type="button">Перейти</button> </body> 

Все скринридеры скроют кнопку. Только не будем спешить радоваться. Я говорил, что большинство интерфейсов разработаны для пользователя с мышкой. Есть функции, которые нужны ему, но полностью бесполезны для пользователей клавиатуры или скринридера. Здесь трюк с aria-hidden="true" tabindex="-1" будет полезен. Но это всё равно хак!

Самый лучший вариант переработать интерфейс так, чтобы были общие паттерны взаимодействия. Да, это дополнительная работа всей команды, но в этом случае результат будет лучше для пользователей.

▍ Заключение

Подведём итог. Ключевые моменты при работе с атрибутом aria-hidden:

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

Если у вас возникли вопросы, то я буду ждать вас в комментариях. Большое спасибо за чтение.

Помоги спутнику бороться с космическим мусором в нашей новой игре! ?


ссылка на оригинал статьи https://habr.com/ru/articles/784542/


Комментарии

Добавить комментарий

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