Перш ніж ми перейдемо до клавіатури, зверніть увагу, що на сучасних пристроях є й інші способи “щось ввести”. Наприклад, люди використовують розпізнавання мовлення (особливо на мобільних пристроях) або копіюють/вставляють за допомогою миші.
Тож якщо ми хочемо відстежувати будь-яке введення даних у поле <input>
, то подій клавіатури недостатньо. Існує ще одна подія під назвою input
для відстеження змін у полі <input>
будь-яким способом. І це може бути кращим рішенням такого завдання. Ми розглянемо цю подію пізніше в розділі Події: change, input, cut, copy, paste.
Події клавіатури слід використовувати, коли ми хочемо обробляти дії клавіатури (віртуальна клавіатура також враховується). Наприклад, щоб реагувати на клавіші зі стрілками Up та Down або гарячі клавіші (включаючи комбінації клавіш).
Тестовий стенд
Щоб краще зрозуміти події на клавіатурі, ви можете використовувати тестовий стенд, наведений нижче.
Спробуйте різні комбінації клавіш у текстовому полі.
kinput.onkeydown = kinput.onkeyup = kinput.onkeypress = handle;
let lastTime = Date.now();
function handle(e) {
if (form.elements[e.type + 'Ignore'].checked) return;
area.scrollTop = 1e6;
let text = e.type +
' key=' + e.key +
' code=' + e.code +
(e.shiftKey ? ' shiftKey' : '') +
(e.ctrlKey ? ' ctrlKey' : '') +
(e.altKey ? ' altKey' : '') +
(e.metaKey ? ' metaKey' : '') +
(e.repeat ? ' (repeat)' : '') +
"\n";
if (area.value && Date.now() - lastTime > 250) {
area.value += new Array(81).join('-') + '\n';
}
lastTime = Date.now();
area.value += text;
if (form.elements[e.type + 'Stop'].checked) {
e.preventDefault();
}
}
#kinput {
font-size: 150%;
box-sizing: border-box;
width: 95%;
}
#area {
width: 95%;
box-sizing: border-box;
height: 250px;
border: 1px solid black;
display: block;
}
form label {
display: inline;
white-space: nowrap;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<form id="form" onsubmit="return false">
Запобігти типовій дії для:
<label>
<input type="checkbox" name="keydownStop" value="1"> keydown</label>
<label>
<input type="checkbox" name="keyupStop" value="1"> keyup</label>
<p>
Ігнорувати:
<label>
<input type="checkbox" name="keydownIgnore" value="1"> keydown</label>
<label>
<input type="checkbox" name="keyupIgnore" value="1"> keyup</label>
</p>
<p>Поставте фокус на полі введення та натисніть клавішу.</p>
<input type="text" placeholder="Натисніть клавіші тут" id="kinput">
<textarea id="area" readonly></textarea>
<input type="button" value="Очистити" onclick="area.value = ''" />
</form>
<script src="script.js"></script>
</body>
</html>
Keydown та keyup
Події keydown
відбуваються, коли клавішу натискають, а потім keyup
, коли її відпускають.
event.code та event.key
Властивість key
об’єкта події дозволяє отримати символ, а властивість code
об’єкта події дозволяє отримати “фізичний код клавіші”.
Наприклад, ту саму клавішу Z можна натискати з або без клавіші Shift. Це дає нам два різних символи: малий z
і великий Z
.
event.key
– це саме символ, і він може бути різним. Але event.code
однаковий:
Клавіша | event.key |
event.code |
---|---|---|
Z | z (нижній регістр) |
KeyZ |
Shift+Z | Z (верхній регістр) |
KeyZ |
Якщо користувач працює з різними мовами, то при перемиканні на іншу мову замість "Z"
буде зовсім інший символ. Він стане значенням event.key
, тоді як event.code
буде завжди однаковим: "KeyZ"
.
Кожна клавіша має код, який залежить від її розташування на клавіатурі. Ключові коди, описані в Специфікації коду UI подій.
Наприклад:
- Клавіші літер мають коди
"Key<літера>"
:"KeyA"
,"KeyB"
і т.д. - Цифрові клавіші мають коди:
"Digit<цифра>"
:"Digit0"
,"Digit1"
і т.д. - Спеціальні клавіші кодуються своїми назвами:
"Enter"
,"Backspace"
,"Tab"
тощо.
Існує кілька поширених розкладок клавіатури, і специфікація містить коди клавіш для кожної з них.
Прочитайте алфавітно-цифровий розділ специфікації, щоб отримати додаткові коди, або просто натисніть клавішу на тестовому стенді вище.
"KeyZ"
, а не "keyZ"
Здається очевидним, але більшість все одно припускається помилок.
Будь ласка, уникайте помилок: це KeyZ
, а не keyZ
. Перевірка на кшталт event.code=="keyZ"
не працюватиме: перша літера "Key"
має бути у верхньому регістрі.
Що робити, якщо клавіша не дає жодного символу? Наприклад, Shift або F1 або інші. Для цих клавіш event.key
приблизно такий самий, як event.code
:
Клавіша | event.key |
event.code |
---|---|---|
F1 | F1 |
F1 |
Backspace | Backspace |
Backspace |
Shift | Shift |
ShiftRight або ShiftLeft |
Зверніть увагу, що event.code
точно визначає, яка клавіша натиснута. Наприклад, більшість клавіатур мають дві клавіші Shift: ліворуч і праворуч. event.code
говорить нам, яка саме була натиснута, а event.key
відповідає за “значення” клавіші: яка це саме клавіша (це “Shift”).
Скажімо, ми хочемо обробити гарячу клавішу: Ctrl+Z (або Cmd+Z для Mac). Більшість текстових редакторів підключають до неї дію “Скасувати”. Ми можемо встановити прослуховувач на keydown
і перевірити, яка клавіша натиснута.
Тут виникає дилема: у такому прослуховуванні ми перевіряємо значення event.key
або event.code
?
З одного боку, значення event.key
є символом, воно змінюється залежно від мови. Якщо відвідувач має кілька мов в ОС і перемикається між ними, одна і та ж клавіша дає різні символи. Тому має сенс перевірити event.code
, він завжди однаковий.
Ось таким чином:
document.addEventListener('keydown', function(event) {
if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
alert('Скасувати!')
}
});
З іншого боку, є проблема з event.code
. Для різних розкладок клавіатури одна і та ж клавіша може мати різні символи.
Наприклад, ось розкладка США (“QWERTY”) і німецька (“QWERTZ”) під нею (з Вікіпедії):
Для тієї ж клавіші американська розкладка має “Z”, а німецька — “Y” (букви змінюються місцями).
Без перебільшень, event.code
буде дорівнювати KeyZ
для людей з німецькою розкладкою, коли вони натиснуть Y.
Якщо ми перевіримо event.code == 'KeyZ'
у нашому коді, то для людей з німецькою розкладкою такий тест пройде, коли вони натиснуть Y.
Звучить дуже дивно, але так і є. У специфікації чітко згадується така поведінка.
Отже, event.code
може збігатися з неправильним символом для неочікуваної розкладки. Одні й ті самі букви в різних розкладках можуть зіставлятися з різними фізичними клавішами, що призводить до різних кодів. На щастя, це відбувається лише з кількома кодами, напр. keyA
, keyQ
, keyZ
(як ми бачили), і не відбувається зі спеціальними клавішами, такими як Shift
. Список можна знайти в специфікації.
Для надійного відстеження символів, що залежать від розкладки, event.key
може бути кращим рішенням.
З іншого боку, event.code
має перевагу, бо залишається завжди однаковим, прив’язаним до місця розташування фізичної клавіші, навіть якщо відвідувач змінює мову. Тому гарячі клавіші, які покладаються на нього, працюють добре навіть у разі перемикання мови.
Чи хочемо ми обробляти клавіші, що залежать від розкладки? Тоді event.key
– це вихід.
Або ми хочемо, щоб гаряча клавіша працювала навіть після зміни мови? Тоді event.code
може бути кращим рішенням.
Автоповтор
Якщо клавіша натискається досить довго, вона починає автоматично повторюватися: keydown
запускається знову і знову, а потім, коли вона відпускається, ми нарешті отримуємо keyup
. Таким чином, це нормально мати багато keydown
і один keyup
.
Для подій, ініційованих автоповтором, об’єкт події має властивість event.repeat
, встановлену на true
.
Типові дії
Типові дії можуть бути різними, оскільки існує багато можливих речей, які можуть бути ініційовані клавіатурою.
Наприклад:
- На екрані з’являється символ (найбільш очевидний результат).
- Символ видалено (Delete клавіша).
- Сторінка прокручується (PageDown клавіша).
- Браузер відкриває діалогове вікно “Зберегти сторінку”. (Ctrl+S)
- …і так далі.
Запобігання типовій дії на keydown
може скасувати більшість з них, за винятком спеціальних клавіш на основі ОС. Наприклад, у Windows Alt+F4 закриває поточне вікно браузера. І немає способу зупинити це, запобігаючи типовій дії у JavaScript.
Наприклад, <input>
нижче очікує номер телефону, тому він не приймає клавіші, крім цифр, +
, ()
або -
:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || ['+','(',')','-'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Номер телефону, будь ласка" type="tel">
Обробник onkeydown
використовує тут checkPhoneKey
для перевірки натиснення клавіші. Якщо вона валідна (від 0..9
або з +-()
), обробник повертає true
, інакше false
.
Як ми знаємо, значення false
, що повертається з обробника подій та присвоєне за допомогою властивості DOM або атрибута, як-от вище, запобігає типовій дії. Саме тому для клавіш, які не проходять тест, у <input>
нічого не з’являється. (Повернуте значення true
ні на що не впливає, має значення лише повернення false
)
Зверніть увагу, що спеціальні клавіші, такі як Backspace, Left, Right, не працюють у полі для введення. Це побічний ефект суворого фільтра checkPhoneKey
. Ці клавіші змушують її повертати false
.
Давайте трохи послабимо фільтр, дозволивши клавіші зі стрілками Left, Right, Backspace та Delete:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') ||
['+','(',')','-','ArrowLeft','ArrowRight','Delete','Backspace'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Номер телефону, будь ласка" type="tel">
Тепер стрілки та видалення працюють добре.
Незважаючи на те, що у нас є фільтр клавіш, все одно можна ввести будь-що за допомогою миші та клацнути правою кнопкою миші + “Вставити”. Мобільні пристрої надають інші засоби для введення значень. Тому фільтр не є надійним на 100%.
Альтернативним підходом було б відстежувати подію oninput
– вона запускається після будь-якої модифікації. Там ми можемо перевірити новий input.value
та змінити його/виділити <input>
, якщо він недійсний. Або ми можемо використовувати обидва обробники подій разом.
Застарілий код
У минулому була подія keypress
, а також keyCode
, charCode
, which
властивості об’єкта події.
Під час роботи з ними було так багато несумісностей у браузерах, що у розробників специфікації не було іншого виходу, окрім як відмовитися від усіх них і створити нові, сучасні події (описані вище в цьому розділі). Старий код все ще працює, оскільки браузери продовжують підтримувати його, але більше немає потреби використовувати його.
Мобільні клавіатури
Під час використання віртуальних/мобільних клавіатур, офіційно відомих як IME (Input-Method Editor), стандарт W3C стверджує, що у події клавіатури e.keyCode
має бути 229
, а e.key
має бути "Unidentified"
.
Хоча деякі з цих клавіатур все ще можуть використовувати правильні значення для e.key
, e.code
, e.keyCode
… під час натискання певних клавіш, таких як стрілки або видалення, немає гарантії, тому ваша логіка клавіатури може не завжди працювати на мобільних пристроях.
Підсумки
Натискання клавіші завжди генерує подію на клавіатурі, будь то клавіші-символи або спеціальні клавіші, як-от Shift або Ctrl тощо. Єдиним винятком є клавіша Fn, яка іноді зустрічається на клавіатурі ноутбука. Для неї немає жодних подій клавіатури, оскільки вони часто реалізуються на нижчому рівні, ніж ОС.
Події клавіатури:
keydown
– при натисканні клавіші (автоматично повторюється, якщо клавіша довго утримується),keyup
– при відпусканні клавіші.
Основні властивості подій клавіатури:
code
– “код клавіші” ("KeyA"
,"ArrowLeft"
і так далі), специфічний для фізичного розташування клавіші на клавіатурі.key
– символ ("A"
,"a"
і так далі), для несимвольних ключів, таких як Esc, зазвичай має те саме значення, що іcode
.
У минулому події клавіатури іноді використовувалися для відстеження даних, які користувач ввів у поля форми. Це ненадійно, оскільки вхідні дані можуть надходити з різних джерел. У нас є події input
та change
для обробки будь-якого введення (розглянуто далі в розділі Події: change, input, cut, copy, paste). Вони запускаються після будь-якого типу введення, включаючи копіювання або розпізнавання мовлення.
Ми повинні використовувати події клавіатури, коли нам дійсно потрібна клавіатура. Наприклад, реагувати на гарячі або спеціальні клавіші.
Коментарі
<code>
, для кількох рядків – обгорніть їх тегом<pre>
, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)