Определение источника фокуса

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

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

  • после нажатия клавиши Tab;
  • при клике мышью;
  • при касании сенсорного экрана;
  • после программного вызова element.focus().

Различение этих сценариев необходимо для корректного поведения интерфейса. Например:

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

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


Проблема определения источника взаимодействия

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

Например:

input.addEventListener('focus', function() {
  console.log('Элемент получил фокус');
});

В обработчике невозможно понять, произошёл ли фокус из-за:

  • клавиатурной навигации,
  • клика мышью,
  • касания,
  • программного вызова.

Попытки решить задачу через прослушивание отдельных событий (keydown, mousedown, touchstart) приводят к ряду проблем:

  1. События происходят в разном порядке в разных браузерах.
  2. Некоторые устройства генерируют несколько типов событий.
  3. Взаимодействие может быть синтетическим (скриптовым).

В результате появляется необходимость в механизме, который:

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

Эта задача решается модулем ally.js, отвечающим за определение источника фокуса.


Механизм отслеживания источника фокуса в ally.js

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

Основные категории взаимодействия:

Тип источника Описание
keyboard фокус появился после клавиатурного действия
mouse фокус появился после клика мышью
touch фокус получен после касания
script фокус установлен программно

Для определения источника ally.js отслеживает несколько ключевых событий:

  • keydown
  • mousedown
  • pointerdown
  • touchstart
  • focus

Алгоритм работы выглядит следующим образом:

  1. Система отслеживает последнее пользовательское действие.
  2. При наступлении события focus анализируется контекст предыдущего события.
  3. Тип взаимодействия сохраняется и становится доступным для логики приложения.

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


Модуль interaction-type

Для определения источника взаимодействия используется модуль:

ally.interactionType

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

Подключение модуля:

import interactionType from 'ally.js/interaction-type';

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

Получение текущего типа взаимодействия:

interactionType.get();

Пример результата:

"keyboard"

или

"mouse"

Возможные значения:

  • keyboard
  • mouse
  • touch
  • pointer
  • script

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


Пример использования

Рассмотрим ситуацию, когда необходимо показывать визуальный индикатор фокуса только при клавиатурной навигации.

Базовая реализация

import interactionType from 'ally.js/interaction-type';

document.addEventListener('focus', function(event) {
  if (interactionType.get() === 'keyboard') {
    event.target.classList.add('focus-visible');
  }
}, true);

CSS-стиль:

.focus-visible {
  outline: 3px solid #4A90E2;
}

В результате:

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

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


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

Модуль позволяет подписаться на изменение типа взаимодействия. Это полезно для глобального управления состоянием интерфейса.

Пример:

interactionType.onChange(function(type) {
  console.log('Тип взаимодействия:', type);
});

При переключении между устройствами ввода в консоли появятся сообщения:

Тип взаимодействия: keyboard
Тип взаимодействия: mouse
Тип взаимодействия: touch

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


Работа с программным фокусом

Отдельную категорию составляет фокус, установленный программно:

element.focus();

В обычном DOM API невозможно отличить такой сценарий от пользовательского. ally.js определяет его через анализ цепочки событий.

Пример:

button.addEventListener('click', function() {
  input.focus();
});

В этом случае источник фокуса будет определён как:

script

Это важно для компонентов интерфейса, которые автоматически перемещают фокус.

Например:

  • модальные окна;
  • диалоговые панели;
  • раскрывающиеся меню;
  • автодополнение.

Использование при управлении модальными окнами

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

Пример:

function openDialog(dialog) {
  dialog.classList.add('open');

  const focusable = dialog.querySelector('button, input, a');
  focusable.focus();
}

Однако в некоторых случаях требуется различать:

  • открытие через клавиатуру;
  • открытие через мышь.

С использованием ally.js:

import interactionType from 'ally.js/interaction-type';

function openDialog(dialog) {
  dialog.classList.add('open');

  const focusable = dialog.querySelector('button, input, a');

  if (interactionType.get() === 'keyboard') {
    focusable.focus();
  }
}

Это позволяет избежать неожиданного изменения фокуса при использовании мыши.


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

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

Определение текущего типа взаимодействия:

const type = interactionType.get();

Применение CSS-класса:

document.body.classList.add('interaction-' + type);

CSS-пример:

.interaction-touch button {
  padding: 16px 20px;
}

.interaction-mouse button {
  padding: 8px 12px;
}

Это позволяет динамически адаптировать интерфейс под текущий способ взаимодействия.


Особенности работы на разных устройствах

Современные устройства могут поддерживать несколько способов ввода одновременно:

  • ноутбуки с тачпадом;
  • планшеты с клавиатурой;
  • гибридные устройства.

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

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

  1. Нажата клавиша Tab → keyboard
  2. Клик мышью → mouse
  3. Касание экрана → touch

Каждое новое действие обновляет текущее состояние.


Преимущества подхода ally.js

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

Кроссбраузерная стабильность

Библиотека учитывает различия в порядке событий и особенностях реализации DOM в разных браузерах.

Поддержка различных устройств ввода

Обрабатываются:

  • мышь;
  • клавиатура;
  • сенсорные экраны;
  • указательные устройства.

Интеграция с системой управления фокусом

Механизм может использоваться совместно с другими модулями ally.js:

  • maintain/tab-focus
  • maintain/disabled
  • query/focusable

Улучшение доступности

Интерфейс корректно реагирует на клавиатурную навигацию, что особенно важно для пользователей:

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

Ограничения и особенности

Несмотря на преимущества, следует учитывать ряд особенностей.

1. Невозможно определить источник фокуса со стопроцентной точностью

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

2. Некоторые браузеры генерируют дополнительные события

Например, сенсорные устройства могут создавать как touch, так и mouse события.

3. Программные действия могут маскироваться под пользовательские

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

Библиотека использует внутренние эвристики для минимизации подобных проблем.


Архитектурная роль определения источника фокуса

В крупных интерфейсах определение источника фокуса является частью более широкой системы управления доступностью. Эта система обычно включает:

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

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