Показ тултипов по фокусу и hover

Тултип — это небольшой всплывающий элемент интерфейса, который отображает поясняющую информацию при взаимодействии пользователя с элементом. Чаще всего тултипы появляются при наведении курсора мыши или при получении элементом фокуса с клавиатуры. В контексте доступности (accessibility) важно обеспечивать корректное отображение тултипов не только для пользователей мыши, но и для тех, кто перемещается по интерфейсу с помощью клавиатуры или вспомогательных технологий.

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

Особенности тултипов с точки зрения доступности

При реализации тултипов необходимо учитывать несколько принципов:

1. Поддержка клавиатурной навигации Информация, отображаемая в тултипе, должна быть доступна при фокусе на элементе, а не только при наведении курсора.

2. Управление фокусом Тултип не должен перехватывать фокус, если он не является интерактивным элементом.

3. Связь с целевым элементом Элемент, вызывающий тултип, должен быть связан с ним через атрибут aria-describedby.

4. Контроль видимости Тултип должен появляться и исчезать предсказуемо, без неожиданных изменений состояния интерфейса.

Библиотека ally.js помогает корректно обрабатывать события фокуса и взаимодействия, обеспечивая согласованность поведения.


Структура HTML для тултипа

Базовая структура включает два элемента: триггер и сам тултип.

<button id="info-button" aria-describedby="tooltip-info">
  Информация
</button>

<div id="tooltip-info" role="tooltip" hidden>
  Дополнительные сведения о действии кнопки
</div>

Ключевые моменты:

  • role="tooltip" сообщает вспомогательным технологиям назначение элемента.
  • aria-describedby связывает кнопку с тултипом.
  • Атрибут hidden используется для скрытия элемента до момента активации.

Показ тултипа при наведении (hover)

Классическая модель поведения тултипа основана на событиях mouseenter и mouseleave.

const button = document.getElementById('info-button');
const tooltip = document.getElementById('tooltip-info');

button.addEventListener('mouseenter', () => {
  tooltip.hidden = false;
});

button.addEventListener('mouseleave', () => {
  tooltip.hidden = true;
});

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


Показ тултипа при получении фокуса

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

button.addEventListener('focus', () => {
  tooltip.hidden = false;
});

button.addEventListener('blur', () => {
  tooltip.hidden = true;
});

Таким образом обеспечивается единая модель поведения для разных способов взаимодействия.


Использование ally.js для отслеживания фокуса

Библиотека ally.js предоставляет инструменты для определения текущего активного элемента и контроля навигации.

Подключение библиотеки:

<script src="https://unpkg.com/ally.js/ally.min.js"></script>

Одной из полезных утилит является observer активного элемента.

const observer = ally.observe.activeElement();

observer.subscribe(function(event) {
  console.log('Текущий элемент фокуса:', event.activeElement);
});

Этот механизм позволяет отслеживать изменения фокуса и реагировать на них централизованно.


Управление тултипами через наблюдение за фокусом

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

const tooltipMap = new Map([
  ['info-button', 'tooltip-info']
]);

const observer = ally.observe.activeElement();

observer.subscribe(event => {
  const id = event.activeElement && event.activeElement.id;

  tooltipMap.forEach((tooltipId, buttonId) => {
    const tooltip = document.getElementById(tooltipId);

    if (id === buttonId) {
      tooltip.hidden = false;
    } else {
      tooltip.hidden = true;
    }
  });
});

Преимущества такого подхода:

  • централизованное управление состояниями;
  • упрощение логики при большом количестве элементов;
  • более предсказуемая работа фокуса.

Определение способа взаимодействия пользователя

Важной задачей является различение hover и focus взаимодействий. Например, пользователь может использовать клавиатуру, сенсорный экран или мышь.

В ally.js существует модуль определения ввода:

const interaction = ally.when.focusable();

interaction.then(element => {
  console.log('Элемент готов к получению фокуса:', element);
});

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


Управление позиционированием тултипа

Позиция тултипа должна соответствовать расположению элемента. Для этого часто используется вычисление координат через getBoundingClientRect.

function positionTooltip(trigger, tooltip) {
  const rect = trigger.getBoundingClientRect();

  tooltip.style.position = 'absolute';
  tooltip.style.top = rect.bottom + window.scrollY + 'px';
  tooltip.style.left = rect.left + window.scrollX + 'px';
}

Вызов функции при отображении:

button.addEventListener('focus', () => {
  tooltip.hidden = false;
  positionTooltip(button, tooltip);
});

Такое позиционирование делает поведение тултипа более устойчивым при прокрутке страницы.


Работа с группами тултипов

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

document.addEventListener('focusin', event => {
  const trigger = event.target.closest('[data-tooltip]');
  if (!trigger) return;

  const tooltip = document.getElementById(trigger.dataset.tooltip);
  tooltip.hidden = false;
});

document.addEventListener('focusout', event => {
  const trigger = event.target.closest('[data-tooltip]');
  if (!trigger) return;

  const tooltip = document.getElementById(trigger.dataset.tooltip);
  tooltip.hidden = true;
});

HTML-разметка:

<button data-tooltip="tooltip-save">Сохранить</button>

<div id="tooltip-save" role="tooltip" hidden>
  Сохраняет текущие изменения
</div>

Этот подход значительно уменьшает количество обработчиков.


Предотвращение проблем с фокусом

Некоторые ошибки при реализации тултипов могут ухудшать доступность интерфейса:

Перехват фокуса тултипом Если тултип получает фокус, пользователь может потерять контекст навигации.

Мгновенное исчезновение Слишком быстрое скрытие тултипа при небольшом смещении курсора.

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

Пример реализации задержки:

let showTimer;

button.addEventListener('mouseenter', () => {
  showTimer = setTimeout(() => {
    tooltip.hidden = false;
  }, 300);
});

button.addEventListener('mouseleave', () => {
  clearTimeout(showTimer);
  tooltip.hidden = true;
});

Комбинированная модель hover + focus

На практике тултип должен поддерживать оба сценария взаимодействия.

function showTooltip() {
  tooltip.hidden = false;
}

function hideTooltip() {
  tooltip.hidden = true;
}

button.addEventListener('mouseenter', showTooltip);
button.addEventListener('mouseleave', hideTooltip);

button.addEventListener('focus', showTooltip);
button.addEventListener('blur', hideTooltip);

Такой подход обеспечивает:

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

Использование ally.js для проверки фокусируемости

Перед назначением тултипа полезно определить, может ли элемент получать фокус.

if (ally.is.focusable(button)) {
  console.log('Элемент доступен для клавиатурной навигации');
}

Это особенно важно при работе с нестандартными элементами интерфейса, созданными на основе div или span.


Расширенные сценарии

В сложных интерфейсах тултипы могут:

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

Пример обработки клавиши Escape:

document.addEventListener('keydown', event => {
  if (event.key === 'Escape') {
    tooltip.hidden = true;
  }
});

Связь тултипов с системой управления фокусом

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

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

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