# Клавиатура: 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)`:
  1. Во всех браузерах, кроме IE, у события `keypress` есть свойство `charCode`, которое содержит код символа.
  2. Браузер 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] ### Отмена любых действий Отменять можно не только символ, а любое действие клавиш. Например: Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь -- те, которые происходят на уровне операционной системы. Комбинация 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` по отпусканию клавиши. ## Итого, рецепты Если обобщить данные из этой таблицы и статьи, то можно сделать ряд выводов:
  1. Для реализации горячих клавиш, включая сочетания -- используем `keydown`.
  2. Если нужен именно символ -- используем `keypress`. При этом функция `getChar` позволит получить символ и отфильтровать лишние срабатывания.
  3. Ловля CapsLock глючит под MacOS. Её можно организовать при помощи проверки `navigator.userAgent` и `navigator.platform`, а лучше вообще не трогать эту клавишу.
Для работы с вводом в формы, существуют [события для форм](/events-change), которые мы разберём позже. Их можно использовать как отдельно от событий клавиатуры, так и вместе с ними. [head] [/head]