Навигация с клавиатуры

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

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

const grid = new Muuri('.grid', {
  dragEnabled: true,
  layoutOnInit: true
});

const items = grid.getItems();
items.forEach(item => {
  item.getElement().setAttribute('tabindex', '0');
});

В приведённом примере каждому элементу присваивается tabindex="0", что делает его доступным для навигации через клавишу Tab.


Обработка событий клавиатуры

Для управления сеткой с клавиатуры используется событие keydown. Его можно привязать как к отдельным элементам, так и к документу в целом.

grid.getItems().forEach(item => {
  const el = item.getElement();
  el.addEventListener('keydown', event => {
    switch(event.key) {
      case 'ArrowUp':
        moveFocus(item, 'up');
        break;
      case 'ArrowDown':
        moveFocus(item, 'down');
        break;
      case 'ArrowLeft':
        moveFocus(item, 'left');
        break;
      case 'ArrowRight':
        moveFocus(item, 'right');
        break;
    }
  });
});

Функция moveFocus отвечает за определение следующего элемента в указанном направлении и передачу ему фокуса.


Определение следующего элемента

Для корректной навигации необходимо вычислить расположение элементов сетки. Muuri предоставляет методы getItems() и getElement() для работы с элементами.

Пример функции для перемещения фокуса:

function moveFocus(currentItem, direction) {
  const items = grid.getItems();
  const currentRect = currentItem.getElement().getBoundingClientRect();

  let targetItem = null;
  let minDistance = Infinity;

  items.forEach(item => {
    if (item === currentItem) return;
    const rect = item.getElement().getBoundingClientRect();
    let distance = Infinity;

    switch(direction) {
      case 'up':
        if (rect.bottom <= currentRect.top) distance = currentRect.top - rect.bottom;
        break;
      case 'down':
        if (rect.top >= currentRect.bottom) distance = rect.top - currentRect.bottom;
        break;
      case 'left':
        if (rect.right <= currentRect.left) distance = currentRect.left - rect.right;
        break;
      case 'right':
        if (rect.left >= currentRect.right) distance = rect.left - currentRect.right;
        break;
    }

    if (distance < minDistance) {
      minDistance = distance;
      targetItem = item;
    }
  });

  if (targetItem) targetItem.getElement().focus();
}

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


Интеграция с drag-and-drop

Если включён dragEnabled, навигация клавиатурой должна корректно взаимодействовать с перетаскиванием. Muuri предоставляет события dragStart, dragEnd и move, которые можно использовать для синхронизации состояния элементов.

Пример приостановки фокуса во время перетаскивания:

grid.on('dragStart', item => {
  item.getElement().setAttribute('tabindex', '-1');
});

grid.on('dragEnd', item => {
  item.getElement().setAttribute('tabindex', '0');
});

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


Оптимизация и доступность

  • Использовать tabindex только для элементов, доступных для навигации.
  • Обрабатывать клавиши Enter и Space для активации элементов.
  • Добавлять ARIA-атрибуты, например role="gridcell", чтобы экранные читалки корректно интерпретировали сетку.
  • При динамическом добавлении элементов сетки не забывать назначать им tabindex и привязывать обработчики клавиатуры.

Пример полной реализации

const grid = new Muuri('.grid', { dragEnabled: true, layoutOnInit: true });

function setupKeyboardNavigation() {
  grid.getItems().forEach(item => {
    const el = item.getElement();
    el.setAttribute('tabindex', '0');
    el.addEventListener('keydown', event => {
      switch(event.key) {
        case 'ArrowUp': moveFocus(item, 'up'); break;
        case 'ArrowDown': moveFocus(item, 'down'); break;
        case 'ArrowLeft': moveFocus(item, 'left'); break;
        case 'ArrowRight': moveFocus(item, 'right'); break;
        case 'Enter': activateItem(item); break;
      }
    });
  });
}

function activateItem(item) {
  console.log('Активирован элемент:', item.getElement());
}

setupKeyboardNavigation();

grid.on('add', () => setupKeyboardNavigation());
grid.on('dragStart', item => item.getElement().setAttribute('tabindex', '-1'));
grid.on('dragEnd', item => item.getElement().setAttribute('tabindex', '0'));

Данный подход позволяет сочетать доступность, удобство навигации и drag-and-drop функциональность в одной сетке Muuri.


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