Поддержка скринридеров

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

ARIA-атрибуты и роли

Для элементов, фильтруемых с помощью Isotope, необходимо определить семантические роли. Основные рекомендации:

  • role="list" — контейнер элементов, которые будут сортироваться или фильтроваться.
  • role="listitem" — каждый отдельный элемент списка. Это позволяет скринридерам воспринимать изменения в структуре списка корректно.
  • aria-hidden="true" — применяется к элементам, которые временно скрыты через Isotope, чтобы скринридеры игнорировали их.
  • aria-live="polite" — контейнеру, в котором динамически изменяется содержимое, чтобы озвучивать пользователю изменения без резкого прерывания текущего потока речи.

Пример базовой структуры с ARIA-атрибутами:

<div id="grid" role="list" aria-live="polite">
  <div class="grid-item" role="listitem">Элемент 1</div>
  <div class="grid-item" role="listitem">Элемент 2</div>
  <div class="grid-item" role="listitem">Элемент 3</div>
</div>

Скрытие и отображение элементов

Isotope использует CSS-свойства для фильтрации элементов (display: none, position: absolute и т.д.), что не всегда корректно воспринимается скринридерами. Рекомендуется:

  • Использовать aria-hidden="true" для элементов, скрываемых Isotope.
  • При повторном отображении элемента — удалять атрибут aria-hidden, чтобы скринридер мог озвучить элемент.
  • Избегать мгновенного удаления DOM-элементов, так как это может вызвать потерю фокуса и озвучивания контента.

Пример обновления атрибутов через JavaScript при фильтрации:

var iso = new Isotope('#grid', {
  itemSelector: '.grid-item',
  layoutMode: 'fitRows'
});

function filterItems(filter) {
  iso.arrange({ filter: filter });

  document.querySelectorAll('.grid-item').forEach(function(item) {
    if (item.matches(filter)) {
      item.removeAttribute('aria-hidden');
    } else {
      item.setAttribute('aria-hidden', 'true');
    }
  });
}

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

Скринридеры ориентируются на текущий фокус в документе. При фильтрации важно учитывать:

  • Фокус должен оставаться на интерактивном элементе, например, на кнопке фильтрации или первом видимом элементе списка.
  • При обновлении содержимого использовать focus() или tabindex="0" для корректной навигации.

Пример переноса фокуса на первый видимый элемент после фильтрации:

iso.on('arrangeComplete', function() {
  var firstVisible = document.querySelector('.grid-item:not([aria-hidden="true"])');
  if (firstVisible) {
    firstVisible.setAttribute('tabindex', '-1');
    firstVisible.focus();
  }
});

Доступные уведомления об изменениях

Для пользователей скринридеров полезно предоставлять краткие уведомления о количестве элементов, оставшихся после фильтрации:

<div id="filter-status" aria-live="polite"></div>
iso.on('arrangeComplete', function(filteredItems) {
  var status = document.getElementById('filter-status');
  status.textContent = filteredItems.length + ' элементов отображается';
});

Использование aria-live="polite" обеспечивает озвучивание изменений без прерывания текущего чтения контента.

Общие рекомендации по интеграции

  • Сохранять последовательность DOM при фильтрации, чтобы скринридер сохранял контекст.
  • Проверять фильтрацию с реальными скринридерами, например NVDA, VoiceOver, JAWS.
  • Не полагаться исключительно на визуальные эффекты для передачи информации. Все интерактивные изменения должны быть отражены через ARIA или видимые текстовые уведомления.
  • Включать поддержку клавиатурной навигации для фильтров и сортировки, используя стандартные события клавиатуры (Enter, Space, Arrow).

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