Прежде чем перейти к клавиатуре, обратите внимание, что на современных устройствах есть и другие способы «ввести что-то». Например, распознавание речи (это особенно актуально на мобильных устройствах) или Копировать/Вставить с помощью мыши.
Поэтому, если мы хотим корректно отслеживать ввод в поле <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;
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"></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
тоже есть проблемы. На разных раскладках к одной и той же клавише могут быть привязаны разные символы.
Например, вот схема стандартной (US) раскладки («QWERTY») и под ней немецкой («QWERTZ») раскладки (из Википедии):
Для одной и той же клавиши в американской раскладке значение event.code
равно «Z», в то время как в немецкой «Y».
Буквально, для пользователей с немецкой раскладкой event.code
при нажатии на Y будет равен KeyZ
.
Если мы будем проверять в нашем коде 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)
- …и так далее.
Предотвращение стандартного действия с помощью event.preventDefault()
работает практически во всех сценариях, кроме тех, которые происходят на уровне операционной системы. Например, комбинация Alt+F4 инициирует закрытие браузера в Windows, что бы мы ни делали в JavaScript.
Для примера, <input>
ниже ожидает телефонный номер, так что ничего кроме чисел, +
, ()
или -
принято не будет:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-';
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Введите телефон" type="tel">
Заметьте, что специальные клавиши, такие как Backspace, Left, Right, Ctrl+V, в этом поле для ввода не работают. Это побочный эффект чересчур жёсткого фильтра checkPhoneKey
.
Добавим ему немного больше свободы:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' ||
key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Введите телефон" type="tel">
Теперь стрелочки и удаление прекрасно работают.
…Впрочем, мы всё равно можем ввести в <input>
что угодно с помощью правого клика мыши и пункта «Вставить» контекстного меню. Так что такой фильтр не обладает 100% надёжностью. Мы можем просто оставить всё как есть, потому что в большинстве случаев это работает. Альтернатива – отслеживать событие input
, оно генерируется после любых изменений в поле <input>
, и мы можем проверять новое значение и подчёркивать/изменять его, если оно не подходит.
«Дела минувших дней»
В прошлом существовало также событие keypress
, а также свойства keyCode
, charCode
, which
у объекта события.
Но количество браузерных несовместимостей при работе с ними было столь велико, что у разработчиков спецификации не было другого выхода, кроме как объявить их устаревшими и создать новые, современные события (которые и описываются в этой главе). Старый код ещё работает, так как браузеры продолжают поддерживать и keypress
, и keyCode
с charCode
, и which
, но более нет никакой необходимости в их использовании.
Итого
Нажатие клавиши всегда генерирует клавиатурное событие, будь то буквенно-цифровая клавиша или специальная типа Shift или Ctrl и т.д. Единственным исключением является клавиша Fn
, которая присутствует на клавиатуре некоторых ноутбуков. События на клавиатуре для неё нет, потому что она обычно работает на уровне более низком, чем даже ОС.
События клавиатуры:
keydown
– при нажатии на клавишу (если клавиша остаётся нажатой, происходит автоповтор),keyup
– при отпускании клавиши.
Главные свойства для работы с клавиатурными событиями:
code
– «код клавиши» ("KeyA"
,"ArrowLeft"
и так далее), особый код, привязанный к физическому расположению клавиши на клавиатуре.key
– символ ("A"
,"a"
и так далее), для не буквенно-цифровых групп клавиш (таких как Esc) обычно имеет то же значение, что иcode
.
В прошлом события клавиатуры иногда использовались для отслеживания ввода данных пользователем в полях формы. Это ненадёжно, потому как ввод данных не обязательно может осуществляться с помощью клавиатуры. Существуют события input
и change
специально для обработки ввода (рассмотренные позже в главе События: change, input, cut, copy, paste). Они срабатывают в результате любого ввода, включая Копировать/Вставить мышью и распознавание речи.
События клавиатуры же должны использоваться только по назначению – для клавиатуры. Например, чтобы реагировать на горячие или специальные клавиши.