Клавиатурная навигация — важный аспект доступности и удобства интерфейсов. При использовании библиотеки Locomotive Scroll управление прокруткой осуществляется через виртуальный скролл, что может нарушать стандартное поведение браузера: стрелки, Page Up/Down, Home/End и Tab могут работать некорректно или вовсе игнорироваться.
Корректная поддержка клавиатуры требует явной реализации, поскольку Locomotive Scroll перехватывает события прокрутки и заменяет их собственной логикой.
При подключении Locomotive Scroll возникают следующие особенности:
Отключается нативный scroll контейнера
События scroll заменяются внутренними
обработчиками
Фокус (focus) может не прокручивать страницу к активному элементу
Клавиши:
ArrowUp / ArrowDownPageUp / PageDownHome / End не приводят к ожидаемому
поведениюПричина — использование трансформаций
(transform: translate) вместо стандартного скролла.
Для восстановления функциональности необходимо вручную обрабатывать
события keydown и синхронизировать их с API Locomotive
Scroll.
import LocomotiveScroll from 'locomotive-scroll';
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true
});
| Клавиша | Действие |
|---|---|
| ArrowDown | Прокрутка вниз |
| ArrowUp | Прокрутка вверх |
| PageDown | Прокрутка на высоту окна |
| PageUp | Прокрутка вверх на экран |
| Home | В начало страницы |
| End | В конец страницы |
window.addEventListener('keydown', (e) => {
const scrollStep = 100;
const pageStep = window.innerHeight;
switch (e.key) {
case 'ArrowDown':
scroll.scrollTo(scroll.scroll.instance.scroll.y + scrollStep);
break;
case 'ArrowUp':
scroll.scrollTo(scroll.scroll.instance.scroll.y - scrollStep);
break;
case 'PageDown':
scroll.scrollTo(scroll.scroll.instance.scroll.y + pageStep);
break;
case 'PageUp':
scroll.scrollTo(scroll.scroll.instance.scroll.y - pageStep);
break;
case 'Home':
scroll.scrollTo(0);
break;
case 'End':
scroll.scrollTo(document.body.scrollHeight);
break;
}
});
Locomotive Scroll поддерживает анимацию прокрутки через параметры:
scroll.scrollTo(target, {
duration: 800,
easing: [0.25, 0.00, 0.35, 1.00],
disableLerp: false
});
Для клавиатурной навигации часто используется более короткая длительность:
duration: 400
При переходе по Tab браузер должен прокручивать страницу к активному элементу. В Locomotive Scroll это не происходит автоматически.
document.addEventListener('focusin', (e) => {
const target = e.target;
if (target && typeof target.getBoundingClientRect === 'function') {
scroll.scrollTo(target, {
offset: -100,
duration: 300
});
}
});
focusin, так как оно
всплываетoffset) для лучшего
позиционированияВажно не перехватывать ввод, если пользователь работает с формами.
window.addEventListener('keydown', (e) => {
const active = document.activeElement;
const isInput =
active.tagName === 'INPUT' ||
active.tagName === 'TEXTAREA' ||
active.isContentEditable;
if (isInput) return;
// логика прокрутки
});
tabindex к интерактивным элементамaria-label, если контент
нестандартныйПри переходе по якорям (#section) стандартный скролл не
работает.
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
scroll.scrollTo(target);
}
});
});
Если внутри страницы есть элементы с собственной прокруткой
(overflow: auto), клавиатурная навигация должна учитывать
это.
function isScrollable(el) {
return el.scrollHeight > el.clientHeight;
}
Частое нажатие клавиш может вызывать множество анимаций.
let isScrolling = false;
window.addEventListener('keydown', (e) => {
if (isScrolling) return;
isScrolling = true;
scroll.scrollTo(...);
setTimeout(() => {
isScrolling = false;
}, 300);
});
const multiplier = e.shiftKey ? 3 : 1;
const scrollStep = 80 * multiplier;
При использовании GSAP или других анимационных библиотек важно:
Ключевые сценарии тестирования:
activeElementfocusclass KeyboardScrollController {
constructor(scroll) {
this.scroll = scroll;
this.bindEvents();
}
bindEvents() {
window.addEventListener('keydown', this.handleKey.bind(this));
}
handleKey(e) {
if (this.isInputActive()) return;
const y = this.scroll.scroll.instance.scroll.y;
switch (e.key) {
case 'ArrowDown':
this.scroll.scrollTo(y + 100);
break;
case 'ArrowUp':
this.scroll.scrollTo(y - 100);
break;
}
}
isInputActive() {
const el = document.activeElement;
return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA';
}
}
const controller = new KeyboardScrollController(scroll);