Пошук уроків, статей та іншого контенту
Навчитеся обробляти натискання клавіш, модифікатори та сценарії керування інтерфейсом з клавіатури.
Події клавіатури виникають, коли користувач взаємодіє з клавішами:
keydown — клавішу натиснуто;
keyup — клавішу відпущено;
beforeinput — браузер ще не змінив значення редагованого поля, але зміна вже готується;
input — значення поля вже змінилося.
Для керування інтерфейсом найчастіше використовують keydown. Він виникає одразу після натискання клавіші й може бути повторений, якщо користувач утримує клавішу.
document.addEventListener("keydown", (event) => {
console.log("Натиснуто:", event.key);
});
document.addEventListener("keyup", (event) => {
console.log("Відпущено:", event.key);
});Об’єкт події містить інформацію про натиснуту клавішу та стан модифікаторів.
event.keykey описує символ або функціональну клавішу з урахуванням розкладки та модифікаторів.
document.addEventListener("keydown", (event) => {
console.log(event.key);
});Приклади значень:
"a" або "ф" — залежно від розкладки;
"A" — якщо натиснуто Shift;
"Enter";
"Escape";
"ArrowUp";
" " — пробіл;
"Backspace".
Якщо програма реагує на те, що саме ввів користувач, зазвичай потрібно використовувати key.
event.codecode описує фізичну клавішу на клавіатурі, незалежно від розкладки.
document.addEventListener("keydown", (event) => {
console.log(event.code);
});Для клавіші в позиції W значенням буде "KeyW" навіть тоді, коли активна українська розкладка і користувач фактично вводить "ц".
code зручно використовувати для:
ігор;
керування персонажем;
гарячих клавіш, прив’язаних до фізичного розташування;
сценаріїв, де розкладка не повинна мати значення.
document.addEventListener("keydown", (event) => {
if (event.code === "KeyW") {
console.log("Натиснуто фізичну клавішу W");
}
});event.repeatВластивість repeat дорівнює true, якщо браузер генерує повторну подію через утримання клавіші.
document.addEventListener("keydown", (event) => {
if (event.repeat) {
console.log("Клавішу утримують");
} else {
console.log("Клавішу натиснуто вперше");
}
});Це корисно, якщо певна дія має виконуватися лише один раз за натискання.
Клавіатурна подія має властивості для перевірки модифікаторів:
event.shiftKey;
event.ctrlKey;
event.altKey;
event.metaKey.
metaKey зазвичай відповідає клавіші Command у macOS і клавіші Windows у Windows/Linux.
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.key === "s") {
console.log("Комбінація Ctrl+S");
}
});Для кросплатформних комбінацій часто перевіряють ctrlKey або metaKey:
document.addEventListener("keydown", (event) => {
const modifierPressed = event.ctrlKey || event.metaKey;
if (modifierPressed && event.key.toLowerCase() === "s") {
event.preventDefault();
console.log("Збереження документа");
}
});Метод getModifierState() дає змогу перевіряти додаткові модифікатори, наприклад CapsLock:
document.addEventListener("keydown", (event) => {
if (event.getModifierState("CapsLock")) {
console.log("Caps Lock увімкнено");
}
});preventDefault() і стандартна поведінкаБраузер має стандартні дії для багатьох комбінацій:
Ctrl+S або Cmd+S відкриває діалог збереження сторінки;
Space на кнопці активує її;
стрілки прокручують сторінку;
Tab переміщує фокус;
Enter у формі може запустити надсилання.
Якщо потрібно замінити стандартну дію власною, викликайте event.preventDefault().
document.addEventListener("keydown", (event) => {
const modifierPressed = event.ctrlKey || event.metaKey;
if (modifierPressed && event.key.toLowerCase() === "s") {
event.preventDefault();
console.log("Документ збережено в застосунку");
}
});Не варто безумовно скасовувати всі клавіатурні події. Це може зробити сторінку непридатною для користувачів клавіатури та допоміжних технологій.
Розглянемо панель, якою можна керувати клавішами:
ArrowLeft і ArrowRight перемикають елементи;
Home переходить до першого елемента;
End — до останнього;
Enter відкриває активний елемент;
Escape закриває панель;
? показує довідку.
<!doctype html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<title>Керування клавіатурою</title>
<style>
body {
font-family: sans-serif;
max-width: 600px;
margin: 40px auto;
padding: 0 16px;
}
.carousel {
border: 2px solid #444;
border-radius: 8px;
padding: 24px;
}
.slide {
display: none;
min-height: 100px;
}
.slide.active {
display: block;
}
.help {
margin-top: 16px;
padding: 12px;
background: #f0f0f0;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<main>
<h1>Клавіатурна панель</h1>
<section
class="carousel"
aria-label="Презентація"
aria-live="polite"
>
<article class="slide active" tabindex="-1">
<h2>Слайд 1</h2>
<p>Основи роботи з подіями клавіатури.</p>
</article>
<article class="slide" tabindex="-1">
<h2>Слайд 2</h2>
<p>Використовуйте стрілки для перемикання.</p>
</article>
<article class="slide" tabindex="-1">
<h2>Слайд 3</h2>
<p>Натисніть Enter для підтвердження.</p>
</article>
<p>
Активний слайд:
<output id="slideNumber">1</output>
</p>
</section>
<section id="help" class="help hidden" aria-live="polite">
Стрілки — перемикання, Home/End — початок або кінець,
Enter — відкрити, Escape — закрити довідку.
</section>
<p id="status" aria-live="polite"></p>
</main>
<script>
const slides = [...document.querySelectorAll(".slide")];
const slideNumber = document.querySelector("#slideNumber");
const status = document.querySelector("#status");
const help = document.querySelector("#help");
let currentIndex = 0;
function showSlide(index) {
currentIndex = index;
slides.forEach((slide, slideIndex) => {
const isActive = slideIndex === currentIndex;
slide.classList.toggle("active", isActive);
slide.setAttribute("aria-hidden", String(!isActive));
});
slideNumber.textContent = String(currentIndex + 1);
status.textContent = `Показано слайд ${currentIndex + 1}`;
}
document.addEventListener("keydown", (event) => {
// Не перехоплюємо клавіші під час введення тексту у формі.
const target = event.target;
const isTextField =
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target.isContentEditable;
if (isTextField) {
return;
}
switch (event.key) {
case "ArrowRight":
event.preventDefault();
showSlide((currentIndex + 1) % slides.length);
break;
case "ArrowLeft":
event.preventDefault();
showSlide((currentIndex - 1 + slides.length) % slides.length);
break;
case "Home":
event.preventDefault();
showSlide(0);
break;
case "End":
event.preventDefault();
showSlide(slides.length - 1);
break;
case "Enter":
event.preventDefault();
status.textContent =
`Відкрито слайд ${currentIndex + 1}`;
break;
case "?":
help.classList.remove("hidden");
break;
case "Escape":
help.classList.add("hidden");
break;
}
});
</script>
</body>
</html>У цьому прикладі обробник зареєстровано на document, тому він отримує події з усієї сторінки. Перед обробкою перевіряється, чи не вводить користувач текст. Це важливо: глобальна комбінація ArrowLeft не повинна ламати редагування поля або переміщення курсора.
Якщо клавіша має сенс лише для одного компонента, краще слухати події на цьому компоненті, а не на всьому документі.
const dialog = document.querySelector("#dialog");
dialog.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeDialog();
}
});Переваги такого підходу:
менше глобальної логіки;
простіше уникати конфліктів;
зрозуміліше, до якого компонента належить поведінка.
Для модального вікна зазвичай також потрібно керувати фокусом: після відкриття перевести фокус усередину, а після закриття повернути його на елемент, який відкрив вікно.
Клавіатурні події отримує елемент, який має фокус, а також його предки під час спливання події.
Фокус можуть отримати:
кнопки;
посилання;
поля форми;
елементи з tabindex;
інші інтерактивні елементи.
Не слід використовувати div замість кнопки лише для того, щоб додати обробник keydown.
Неправильний варіант:
<div id="saveButton">Зберегти</div>Кращий варіант:
<button type="button" id="saveButton">Зберегти</button>Кнопка вже має правильну поведінку:
доступна через Tab;
активується клавішею Enter;
активується пробілом;
зрозуміла допоміжним технологіям.
Якщо створення власного віджета справді необхідне, потрібно забезпечити фокус і клавіатурну поведінку вручну:
<div
id="customControl"
role="button"
tabindex="0"
aria-pressed="false"
>
Увімкнути
</div>const control = document.querySelector("#customControl");
function toggleControl() {
const pressed = control.getAttribute("aria-pressed") === "true";
control.setAttribute("aria-pressed", String(!pressed));
control.textContent = pressed ? "Увімкнути" : "Вимкнути";
}
control.addEventListener("click", toggleControl);
control.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
toggleControl();
}
});Однак за можливості використовуйте звичайний <button>, а не імітуйте його поведінку.
event.targetПодія клавіатури спливає від елемента, який має фокус, до його предків.
document.querySelector("#editor").addEventListener("keydown", (event) => {
console.log("Подію отримав редактор");
console.log("Фактичний елемент:", event.target);
});event.target — елемент, на якому подія виникла.
event.currentTarget — елемент, на якому встановлено поточний обробник.
const container = document.querySelector("#container");
container.addEventListener("keydown", (event) => {
console.log(event.target);
console.log(event.currentTarget);
});Якщо всередині контейнера є кілька полів, можна використати делегування подій:
document.querySelector("#menu").addEventListener("keydown", (event) => {
const item = event.target.closest("[role='menuitem']");
if (!item) {
return;
}
if (event.key === "Enter") {
event.preventDefault();
item.click();
}
});Не всі натискання клавіш призводять до введення символів. Наприклад:
Shift;
Control;
Alt;
стрілки;
Escape;
F1.
І навпаки, один символ може вводитися різними способами: фізичною клавішею, вставленням, автозаміною або засобами введення складних текстів.
Тому для валідації та реагування на зміну тексту краще використовувати input, а не keydown.
const searchInput = document.querySelector("#search");
searchInput.addEventListener("input", (event) => {
const query = event.target.value.trim();
console.log("Поточний пошуковий запит:", query);
});Під час введення східноазійських мов або використання інших систем композиції текст ще може формуватися. Для таких сценаріїв існують події compositionstart, compositionupdate і compositionend.
const input = document.querySelector("#name");
let isComposing = false;
input.addEventListener("compositionstart", () => {
isComposing = true;
});
input.addEventListener("compositionend", () => {
isComposing = false;
});
input.addEventListener("input", () => {
if (isComposing) {
return;
}
console.log("Текст готовий до обробки:", input.value);
});Якщо користувач утримує клавішу, браузер може генерувати багато подій keydown, у яких event.repeat дорівнює true.
Для одноразової дії:
document.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.repeat) {
submitForm();
}
});Для безперервного руху в грі або анімації краще не покладатися на частоту keydown. Зберігайте стан клавіш, а рух оновлюйте в циклі анімації.
const pressedKeys = new Set();
document.addEventListener("keydown", (event) => {
pressedKeys.add(event.code);
});
document.addEventListener("keyup", (event) => {
pressedKeys.delete(event.code);
});
function update() {
if (pressedKeys.has("ArrowRight")) {
console.log("Об’єкт рухається праворуч");
}
if (pressedKeys.has("ArrowLeft")) {
console.log("Об’єкт рухається ліворуч");
}
requestAnimationFrame(update);
}
update();Під час втрати фокусу або перемикання вкладки може не прийти очікуваний keyup. Щоб не залишити клавішу в стані «натиснуто», очищуйте стан на blur:
window.addEventListener("blur", () => {
pressedKeys.clear();
});Гарячі клавіші варто проєктувати обережно:
Перевіряйте модифікатори.
Не перехоплюйте клавіші в полях введення без потреби.
Не використовуйте комбінації, які часто потрібні браузеру або операційній системі.
Дозволяйте користувачу виконати ту саму дію через інтерфейс.
Показуйте доступні комбінації в довідці.
Приклад гарячої клавіші для відкриття пошуку:
document.addEventListener("keydown", (event) => {
const target = event.target;
const isTyping =
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target.isContentEditable;
if (isTyping) {
return;
}
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
const searchInput = document.querySelector("#search");
searchInput?.focus();
}
});keypressПодія keypress застаріла й не повинна використовуватися в новому коді. Для фізичного натискання використовуйте keydown і keyup, а для зміни тексту — input.
keyCode або whichВластивості keyCode, which і charCode застаріли. Використовуйте:
event.key для символу або назви клавіші;
event.code для фізичної клавіші.
preventDefault()Якщо власна дія замінює стандартну поведінку браузера, стандартну дію потрібно явно скасувати. Наприклад, обробка Ctrl+S без preventDefault() може додатково відкрити діалог збереження сторінки.
Обробник на document бачить майже всі натискання. Якщо не перевіряти event.target, можна випадково зламати:
введення тексту;
навігацію стрілками в полі;
комбінації браузера;
роботу сторонніх компонентів.
key замість code або навпакиЯкщо потрібно розрізняти введений символ, використовуйте key. Якщо важлива фізична позиція клавіші, використовуйте code.
Не створюйте інтерактивні елементи з div, якщо достатньо <button>, <a> або іншого семантичного HTML-елемента. Клавіатурна підтримка має доповнювати семантичний інтерфейс, а не приховувати його.
keydownkeydown не охоплює вставлення тексту, автозаміни та всі способи введення символів. Для реакції на актуальне значення поля використовуйте input.
Створіть компонент списку завдань із такими можливостями:
ArrowUp і ArrowDown переміщують активний пункт;
Enter перемикає стан завдання;
Delete видаляє активний пункт;
Escape очищає активний вибір;
у текстовому полі клавіші не повинні перехоплюватися глобальним обробником;
активний пункт має бути видимим для користувача та доступним з клавіатури.
Під час реалізації зверніть увагу на:
різницю між event.key і event.code;
перевірку event.target;
використання preventDefault() лише там, де це потрібно;
правильне керування фокусом;
застосування семантичних HTML-елементів.
keydown спрацьовує під час натискання клавіші, а keyup — під час її відпускання.
event.key описує введений символ або функціональну клавішу.
event.code описує фізичну клавішу незалежно від розкладки.
shiftKey, ctrlKey, altKey і metaKey допомагають обробляти комбінації.
preventDefault() скасовує стандартну дію браузера.
event.repeat показує, що клавішу утримують.
Для зміни тексту використовуйте подію input, а не лише keydown.
Глобальні обробники потрібно обмежувати, щоб не заважати полям введення.
Семантичні елементи на кшталт <button> зазвичай уже мають правильну клавіатурну поведінку.
Доступний інтерфейс має працювати не лише мишею, а й клавіатурою.