# Клавиатура: keyup, keydown, keypress
Здесь мы рассмотрим основные "клавиатурные" события и работу с ними.
[cut]
## Тестовый стенд [#keyboard-test-stand]
Для того, чтобы лучше понять, как работают события клавиатуры, можно использовать тестовый стенд.
Попробуйте различные варианты нажатия клавиш в текстовом поле.
[example src="keyboard-dump"]
По мере чтения статьи, если возникнут вопросы -- возвращайтесь к этому стенду.
## События keydown и keyup
События `keydown/keyup` происходят при нажатии/отпускании клавиши и позволяют получить её *скан-код* в свойстве `keyCode`.
Скан-код клавиши одинаков в любой раскладке и в любом регистре. Например, клавиша [key z] может означать символ `"z"`, `"Z"` или `"я"`, `"Я"` в русской раскладке, но её *скан-код* будет всегда одинаков: `90`.
В действии:
```html
```
### Какими бывают скан-коды?
Для буквенно-цифровых клавиш, скан-код будет равен коду соответствующей заглавной английской буквы/цифры.
Например, при нажатии клавиши [key S] (не важно, каков регистр и раскладка) её скан-код будет равен `"S".charCodeAt(0)`.
С другими символами, например, знаками пунктуации, ситуация тоже понятна. Есть таблица кодов, которую можно взять, например, из статьи Джона Уолтера: JavaScript Madness: Keyboard Events, или же можно нажать на нужную клавишу на [тестовом стенде](#keyboard-test-stand) и получить код.
Когда-то в этих кодах была масса кросс-браузерных несовместимостей. Сейчас всё проще -- таблицы кодов в различных браузерах почти полностью совпадают.
Но некоторые несовместимости, всё же, остались. Вы можете увидеть их в таблице ниже. Слева -- клавиша с символом, а справа -- скан-коды в различных браузерах.
Клавиша
Firefox
Остальные браузеры
[key ;]
59
186
[key =]
107
187
[key -]
109
189
## Событие keypress
Событие `keypress` возникает сразу после `keydown`, если нажата *символьная* клавиша, т.е. нажатие приводит к появлению символа.
Любые буквы, цифры генерируют `keypress`. Управляющие клавиши, такие как [key Ctrl], [key Shift], [key F1], [key F2].. -- `keypress` не генерируют.
Событие `keypress` позволяет получить *код символа*. В отличие от скан-кода, он специфичен именно для символа и различен для `"z"` и `"я"`.
Код символа хранится в свойствах: `charCode` и `which`. Здесь скрывается целое "гнездо" кросс-браузерных несовместимостей, разбираться с которыми нет никакого смысла -- запомнить сложно, а на практике нужна лишь одна "правильная" функция, позволяющая получить код везде.
### Получение символа в keypress [#getChar]
Кросс-браузерная функция для получения символа из события `keypress`:
```js
//+ autorun
// event.type должен быть keypress
function getChar(event) {
if (event.which == null) { // IE
if (event.keyCode < 32) return null; // спец. символ
return String.fromCharCode(event.keyCode)
}
if (event.which!=0 && event.charCode!=0) { // все кроме IE
if (event.which < 32) return null; // спец. символ
return String.fromCharCode(event.which); // остальные
}
return null; // спец. символ
}
```
Для общей информации -- вот основные браузерные особенности, учтённые в `getChar(event)`:
Во всех браузерах, кроме IE, у события `keypress` есть свойство `charCode`, которое содержит код символа.
Браузер IE для `keypress` не устанавливает `charCode`, а вместо этого он записывает код символа в `keyCode` (в `keydown/keyup` там хранится скан-код).
Также, посмотрите -- в функции выше используется проверка `if(event.which!=0)`, а не более короткая `if(event.which)`. Это не случайно! При `event.which=null` первое сравнение даст `true`, а второе -- `false`.
В действии:
```html
```
[warn header="Неправильный `getChar`"]
В сети вы можете найти другую функцию того же назначения:
```js
function getChar(event) {
return String.fromCharCode(event.keyCode || event.charCode);
}
```
Она работает неверно для многих специальных клавиш, потому что не фильтрует их. Например, она возвращает символ амперсанда `"&"`, когда нажата клавиша 'Стрелка Вверх'.
[/warn]
[smart header="Свойства-модификаторы"]
Как и у других событий, связанных с пользовательским вводом, поддерживаются свойства `shiftKey`, `ctrlKey`, `altKey` и `metaKey`.
Они установлены в `true`, если нажаты клавиши-модификаторы -- соответственно, [key Shift], [key Ctrl], [key Alt] и [key Cmd] для Mac.
[/smart]
## Отмена пользовательского ввода
Появление символа можно предотвратить, если отменить действие браузера на `keydown/keypress`:
```html
Попробуйте что-нибудь ввести в этих полях:
```
Попробуйте что-нибудь ввести в этих полях (не получится):
При тестировании на стенде вы можете заметить, что отмена действия браузера при `keydown` также предотвращает само событие `keypress`.
[warn header="При `keydown/keypress` значение ещё старое"]
На момент срабатывания `keydown/keypress` *клавиша ещё не обработана браузером*.
Поэтому в обработчике значение `input.value` -- старое, т.е. до ввода. Это можно увидеть в примере ниже. Вводите символы `abcd..`, а справа будет текущее `input.value`: `abc..`
А что, если мы хотим обработать `input.value` именно после ввода? Самое простое решение -- использовать событие `keyup`, либо запланировать обрабочик через `setTimeout(..,0)`.
[/warn]
[warn header="Не для работы с формами"]
Распространённая ошибка -- использовать события клавиатуры для работы с полями ввода в формах.
Это неправильно. События клавиатуры предназначены именно для работы с клавиатурой. Да, их можно использовать и для ``, но будут побочные эффекты.
Например, текст может быть вставлен мышкой, при помощи правого клика и меню, без единого нажатия клавиши. И как нам помогут события клавиатуры?
Некоторые мобильные устройства не генерируют `keypress/keydown`, а сразу вставляют текст в поле. Отменить ввод для них таким образом нельзя, как и вообще отловить его.
Далее мы разберём [события для элементов форм](/events-change), которые позволяют работать с вводом в формы правильно.
[/warn]
### Отмена любых действий
Отменять можно не только символ, а любое действие клавиш.
Например:
При отмене [key Backspace] -- символ не удалится.
При отмене [key PageDown] -- страница не прокрутится.
При отмене [key Tab] -- курсор не перейдёт на следующее поле.
Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь -- те, которые происходят на уровне операционной системы. Комбинация Alt+F4 инициирует закрытие браузера в Windows, что бы мы ни делали в JavaScript.
### Демо: перевод символа в верхний регистр
В примере ниже действие браузера отменяется с помощью `return false`, а вместо него в `input` добавляется значение в верхнем регистре:
```html
```
В действии:
## Особенности клавиш и ОС [#keyboard-events-order]
События `keydown/keyup` возникают всегда. А `keypress` -- по-разному.
Есть три основных категории клавиш, работа с которыми отличается.
Перечислим их в таблице, обращая основное внимание на особенности работы с ними.
Категория
Примеры
События
Описание
Печатные клавиши
[key S] [key 1] [key ,]
`keydown`
`keypress`
`keyup`
Нажатие вызывает `keydown` и `keypress`.
Когда клавишу отпускают, срабатывает `keyup`.
Исключение -- CapsLock под MacOS, он глючит неимоверно:
В Safari/Chrome/Opera: при включении только `keydown`, при отключении только `keyup`.
В Firefox: при включении и отключении только `keydown`.
Попробуйте на тестовом стенде.
Специальные клавиши
[key Alt] [key Esc] [key ⇧]
`keydown`
`keyup`
Нажатие вызывает `keydown`.
Когда клавишу отпускают, срабатывает `keyup`.
Некоторые браузеры могут дополнительно генерировать и `keypress`, например IE для [key Esc]. Попробуйте нажать [key Esc] на тестовом стенде в разных браузерах -- и увидите: в IE `keypress` есть, а в остальных -- нет.
На практике это не доставляет проблем, так как для специальных клавиш мы всегда используем `keydown/keyup`.
Сочетания с печатной клавишей
[key Alt+E]
[key Ctrl+У]
[key Cmd+1]
`keydown`
`keypress?`
`keyup`
Браузеры под Windows -- не генерируют `keypress`, браузеры под MacOS -- генерируют.
Кроме того, если сочетание вызвало браузерное действие или диалог ("Сохранить файл", "Открыть" и т.п., ряд диалогов можно отменить при `keydown`), то `keypress/keyup` может не быть.
## Автоповтор
При долгом нажатии клавиши возникает *автоповтор*. По стандарту, должны генерироваться многократные события `keydown (+keypress)`, и вдобавок стоять свойство [repeat=true](http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent-repeat) у объекта события.
То есть поток событий должен быть такой:
```
keydown
keypress
keydown
keypress
..повторяется, пока клавиша не отжата...
keyup
```
Однако в реальности на это полагаться нельзя. На момент написания статьи, под Firefox(Linux) генерируется и `keyup`:
```
keydown
keypress
keyup
keydown
keypress
keyup
..повторяется, пока клавиша не отжата...
keyup
```
...А Chrome под MacOS не генерирует `keypress`. В общем, "зоопарк". Полагаться можно только на `keydown` при каждом автонажатии и `keyup` по отпусканию клавиши.
## Итого, рецепты
Если обобщить данные из этой таблицы и статьи, то можно сделать ряд выводов:
Для реализации горячих клавиш, включая сочетания -- используем `keydown`.
Если нужен именно символ -- используем `keypress`. При этом функция `getChar` позволит получить символ и отфильтровать лишние срабатывания.
Ловля CapsLock глючит под MacOS. Её можно организовать при помощи проверки `navigator.userAgent` и `navigator.platform`, а лучше вообще не трогать эту клавишу.
Для работы с вводом в формы, существуют [события для форм](/events-change), которые мы разберём позже. Их можно использовать как отдельно от событий клавиатуры, так и вместе с ними.
[head]
[/head]