Screen reader поддержка

При разработке SPA (Single Page Application) с использованием Navigo навигация происходит без полной перезагрузки страницы, что создаёт потенциальные проблемы для пользователей с ограниченными возможностями, особенно для тех, кто использует экранные читалки. Screen reader опирается на изменения в DOM и уведомления о фокусе, поэтому важно правильно обновлять состояние страницы при смене маршрутов.

Основная задача — обеспечить, чтобы при переходе по маршрутам:

  • Пользователь был уведомлён о смене контента.
  • Фокус устанавливался на основную область нового контента.
  • Атрибуты ARIA корректно сообщали о роли и состоянии элементов.

Установка и базовая настройка Navigo

import Navigo from 'navigo';

const router = new Navigo('/', { hash: true });

router
  .on('/', () => loadPage('home'))
  .on('/about', () => loadPage('about'))
  .on('/contact', () => loadPage('contact'))
  .resolve();

Ключевой момент: после каждого вызова loadPage необходимо обновлять фокус и уведомлять screen reader о смене контента.


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

Для правильной поддержки screen reader важно установить фокус на основной контейнер нового контента. Обычно используют элемент <main> или отдельный div с атрибутом role="main":

function loadPage(page) {
  const main = document.querySelector('main');
  
  // Очистка текущего содержимого
  main.innerHTML = '';

  // Подгрузка нового контента
  fetch(`/pages/${page}.html`)
    .then(response => response.text())
    .then(html => {
      main.innerHTML = html;

      // Обновление атрибута для screen reader
      main.setAttribute('tabindex', '-1');
      main.focus();
    });
}

Объяснение:

  • tabindex="-1" позволяет программно устанавливать фокус на элемент, который обычно не доступен через табуляцию.
  • main.focus() уведомляет screen reader о появлении нового содержимого.

Использование ARIA для улучшения навигации

Для SPA особенно важны ARIA-live регионы, чтобы screen reader автоматически озвучивал обновления:

<div id="aria-notifier" aria-live="polite" style="position:absolute;left:-9999px;"></div>
function announce(message) {
  const notifier = document.getElementById('aria-notifier');
  notifier.textContent = '';
  setTimeout(() => {
    notifier.textContent = message;
  }, 100);
}

function loadPage(page) {
  const main = document.querySelector('main');
  
  fetch(`/pages/${page}.html`)
    .then(response => response.text())
    .then(html => {
      main.innerHTML = html;
      main.setAttribute('tabindex', '-1');
      main.focus();

      announce(`Загружена страница: ${page}`);
    });
}

Принцип работы: Screen reader отслеживает изменения в элементе с aria-live="polite" и озвучивает новые сообщения. Это обеспечивает информирование пользователя о смене контента без необходимости полной перезагрузки страницы.


Управление фокусом при динамических элементах

Если на странице присутствуют модальные окна, дропдауны или динамические списки, необходимо корректно управлять фокусом и атрибутами ARIA:

function openModal(modalId) {
  const modal = document.getElementById(modalId);
  modal.style.display = 'block';
  modal.setAttribute('aria-hidden', 'false');

  // Перемещение фокуса на заголовок модального окна
  const heading = modal.querySelector('h2');
  heading.setAttribute('tabindex', '-1');
  heading.focus();
}

function closeModal(modalId) {
  const modal = document.getElementById(modalId);
  modal.style.display = 'none';
  modal.setAttribute('aria-hidden', 'true');

  // Возврат фокуса к кнопке открытия
  const trigger = document.querySelector(`[data-target="${modalId}"]`);
  trigger.focus();
}

Важно: screen reader читает элементы с aria-hidden="false" и игнорирует скрытые элементы. Установка фокуса на заголовок модального окна позволяет пользователю сразу понять контекст.


Навигация через клавиатуру

Для полной доступности необходимо гарантировать, что все маршруты и интерактивные элементы доступны через Tab/Shift+Tab, а динамический контент не теряется при перемещении фокуса. Для этого используют:

  • tabindex="0" для элементов, которые должны быть доступными для табуляции.
  • Обработку событий клавиатуры для кастомных компонентов:
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    const modal = document.querySelector('.modal[aria-hidden="false"]');
    if (modal) closeModal(modal.id);
  }
});

Интеграция с Navigo и SEO-дружественные ссылки

Хотя основное предназначение Navigo — клиентская маршрутизация, важно поддерживать:

  • Чистые URL (router.navigate('/about')) для корректной истории браузера.
  • Обновление атрибутов document.title и meta description для SEO и screen reader:
router.on('/about', () => {
  document.title = 'О нас';
  loadPage('about');
});

Вывод: изменение заголовка страницы помогает screen reader сообщать пользователю контекст текущей страницы, особенно при переключении между маршрутами.


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

  1. Фокус на основном контенте — всегда после изменения маршрута.
  2. ARIA-live уведомления — информируют о динамических изменениях.
  3. Доступ через клавиатуру — Tab, Shift+Tab, Escape для модальных окон.
  4. Семантическая разметка<main>, <header>, <nav> повышает доступность.
  5. Обновление document.title и ARIA-атрибутов — позволяет screen reader правильно интерпретировать контент.

Эти методы обеспечивают корректную работу Navigo в SPA с полной поддержкой screen reader, создавая инклюзивный пользовательский опыт для людей с ограничениями зрения.