Библиотека GridStack.js предназначена для построения интерактивных сеточных интерфейсов с возможностью перемещения и изменения размеров виджетов. По умолчанию большинство действий осуществляется с помощью мыши или сенсорных жестов, однако для полноценных интерфейсов управления, административных панелей и приложений с повышенными требованиями к доступности необходима поддержка клавиатурной навигации.
Клавиатурная навигация позволяет:
GridStack не предоставляет полноценной встроенной системы управления сеткой с клавиатуры, однако архитектура библиотеки позволяет реализовать её на уровне событий DOM и API GridStack.
Основой такой системы являются:
Каждый элемент сетки в GridStack представлен DOM-узлом с классом:
.grid-stack-item
Чтобы элемент мог получать фокус с клавиатуры, ему необходимо назначить атрибут:
tabindex="0"
Пример виджета:
<div class="grid-stack-item" gs-x="0" gs-y="0" gs-w="2" gs-h="2" tabindex="0">
<div class="grid-stack-item-content">
Виджет
</div>
</div>
Теперь элемент может получать фокус при навигации клавишей Tab.
Для автоматического назначения атрибута можно использовать инициализацию после создания сетки.
const grid = GridStack.init();
document.querySelectorAll('.grid-stack-item').forEach(el => {
el.setAttribute('tabindex', '0');
});
Фокусируемый элемент становится отправной точкой для клавиатурных действий.
Для управления выбранным элементом необходимо хранить ссылку на активный widget.
let activeWidget = null;
document.querySelector('.grid-stack').addEventListener('focusin', (event) => {
const item = event.target.closest('.grid-stack-item');
if (item) {
activeWidget = item;
}
});
При получении фокуса переменная activeWidget будет
указывать на текущий элемент сетки.
Основной механизм взаимодействия — обработчик
keydown.
document.addEventListener('keydown', (event) => {
if (!activeWidget) return;
switch (event.key) {
case 'ArrowLeft':
moveWidget(-1, 0);
break;
case 'ArrowRight':
moveWidget(1, 0);
break;
case 'ArrowUp':
moveWidget(0, -1);
break;
case 'ArrowDown':
moveWidget(0, 1);
break;
}
});
Стрелки используются для перемещения виджета внутри сетки.
GridStack хранит координаты элемента внутри объекта GridStackNode.
Получить его можно через DOM-элемент.
const node = activeWidget.gridstackNode;
Основные параметры:
| Параметр | Назначение |
|---|---|
| x | позиция по горизонтали |
| y | позиция по вертикали |
| w | ширина |
| h | высота |
Для перемещения используется метод:
grid.move()
Функция перемещения:
function moveWidget(dx, dy) {
const node = activeWidget.gridstackNode;
const newX = node.x + dx;
const newY = node.y + dy;
grid.move(activeWidget, newX, newY);
}
Теперь стрелки клавиатуры перемещают виджет по сетке.
Без ограничений элемент может выйти за границы сетки. Перед перемещением выполняется проверка.
function moveWidget(dx, dy) {
const node = activeWidget.gridstackNode;
let newX = node.x + dx;
let newY = node.y + dy;
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX + node.w > grid.engine.column)
newX = grid.engine.column - node.w;
grid.move(activeWidget, newX, newY);
}
Такое ограничение предотвращает выход виджета за пределы сетки.
Для управления размерами удобно использовать комбинацию клавиш Shift + стрелка.
Обработчик:
document.addEventListener('keydown', (event) => {
if (!activeWidget) return;
if (event.shiftKey) {
switch (event.key) {
case 'ArrowRight':
resizeWidget(1, 0);
break;
case 'ArrowLeft':
resizeWidget(-1, 0);
break;
case 'ArrowDown':
resizeWidget(0, 1);
break;
case 'ArrowUp':
resizeWidget(0, -1);
break;
}
}
});
Функция изменения размеров:
function resizeWidget(dw, dh) {
const node = activeWidget.gridstackNode;
let newW = node.w + dw;
let newH = node.h + dh;
if (newW < 1) newW = 1;
if (newH < 1) newH = 1;
grid.resize(activeWidget, newW, newH);
}
Чтобы избежать некорректных размеров, используются параметры:
minWminHmaxWmaxHОни могут задаваться в атрибутах:
<div class="grid-stack-item"
gs-w="2"
gs-h="2"
gs-min-w="1"
gs-max-w="6">
</div>
В функции изменения размера можно учитывать ограничения.
if (node.maxW && newW > node.maxW) newW = node.maxW;
if (node.minW && newW < node.minW) newW = node.minW;
Стандартная клавиша Tab перемещает фокус по DOM-структуре. Однако порядок может не совпадать с визуальной сеткой.
Для перемещения по сетке удобнее реализовать навигацию стрелками без перемещения виджета.
Например, при нажатии Alt + стрелка.
if (event.altKey) {
switch (event.key) {
case 'ArrowRight':
focusNeighbor(1,0);
break;
case 'ArrowLeft':
focusNeighbor(-1,0);
break;
}
}
Поиск соседнего элемента:
function focusNeighbor(dx, dy) {
const node = activeWidget.gridstackNode;
const targetX = node.x + dx;
const targetY = node.y + dy;
const widgets = grid.engine.nodes;
const neighbor = widgets.find(w =>
w.x === targetX && w.y === targetY
);
if (neighbor && neighbor.el) {
neighbor.el.focus();
}
}
Для удобства навигации активный элемент должен выделяться.
CSS:
.grid-stack-item:focus {
outline: 3px solid #4a90e2;
}
Можно также добавлять отдельный класс:
document.addEventListener('focusin', (event) => {
document
.querySelectorAll('.grid-stack-item')
.forEach(el => el.classList.remove('active-widget'));
const item = event.target.closest('.grid-stack-item');
if (item) {
item.classList.add('active-widget');
}
});
CSS:
.active-widget {
box-shadow: 0 0 0 3px #2196f3;
}
Иногда требуется режим, при котором клавиатура не перемещает элементы.
GridStack поддерживает блокировку:
grid.disableMove(true);
grid.disableResize(true);
Клавиатурная логика может проверять состояние:
if (grid.opts.disableDrag) return;
Для интерфейсов панелей управления часто используется режим редактирования.
В обычном режиме:
В режиме редактирования:
Пример переключения режима:
let editMode = false;
function toggleEditMode() {
editMode = !editMode;
}
В обработчике клавиш:
if (!editMode) return;
Удаление может выполняться клавишей Delete.
document.addEventListener('keydown', (event) => {
if (!activeWidget) return;
if (event.key === 'Delete') {
grid.removeWidget(activeWidget);
activeWidget = null;
}
});
Можно реализовать создание элемента, например клавишей Insert.
document.addEventListener('keydown', (event) => {
if (event.key === 'Insert') {
const widget = grid.addWidget({
w: 2,
h: 2,
content: 'Новый виджет'
});
widget.setAttribute('tabindex','0');
widget.focus();
}
});
При активном управлении с клавиатуры важно избегать лишних перерасчётов сетки.
Рекомендуется:
grid.batchUpdate()grid.engine напрямую без необходимостиПример:
grid.batchUpdate();
grid.move(widget, x, y);
grid.commit();
Для соответствия стандартам ARIA элементы сетки могут иметь роли.
<div class="grid-stack" role="grid">
Виджет:
<div class="grid-stack-item" role="gridcell">
Активный элемент:
aria-selected="true"
Это позволяет экранным читателям корректно интерпретировать интерфейс.
Полноценная система управления GridStack с клавиатуры обычно включает:
Модуль фокуса
Модуль навигации
Модуль трансформации
Модуль режимов
Такое разделение упрощает поддержку сложных интерфейсов.
const grid = GridStack.init();
let activeWidget = null;
document.querySelector('.grid-stack')
.addEventListener('focusin', e => {
const item = e.target.closest('.grid-stack-item');
if (item) activeWidget = item;
});
document.addEventListener('keydown', e => {
if (!activeWidget) return;
const node = activeWidget.gridstackNode;
switch (e.key) {
case 'ArrowLeft':
grid.move(activeWidget, node.x - 1, node.y);
break;
case 'ArrowRight':
grid.move(activeWidget, node.x + 1, node.y);
break;
case 'ArrowUp':
grid.move(activeWidget, node.x, node.y - 1);
break;
case 'ArrowDown':
grid.move(activeWidget, node.x, node.y + 1);
break;
}
});
Такая архитектура служит базой для построения более сложной системы клавиатурного управления, включающей управление режимами, ограничениями сетки и поддержкой доступности.