17 KiB
Клавиатура: keyup, keydown, keypress
Здесь мы рассмотрим основные "клавиатурные" события и работу с ними. [cut]
Тестовый стенд [#keyboard-test-stand]
Для того, чтобы лучше понять, как работают события клавиатуры, можно использовать тестовый стенд.
Попробуйте различные варианты нажатия клавиш в текстовом поле.
[example src="keyboard-dump"]
По мере чтения статьи, если возникнут вопросы -- возвращайтесь к этому стенду.
События keydown и keyup
События keydown/keyup
происходят при нажатии/отпускании клавиши и позволяют получить её скан-код в свойстве keyCode
.
Скан-код клавиши одинаков в любой раскладке и в любом регистре. Например, клавиша [key z] может означать символ "z"
, "Z"
или "я"
, "Я"
в русской раскладке, но её скан-код будет всегда одинаков: 90
.
В действии:
<input onkeydown="this.nextSibling.innerHTML = event.keyCode"><b></b>
Какими бывают скан-коды?
Для буквенно-цифровых клавиш, скан-код будет равен коду соответствующей заглавной английской буквы/цифры.
Например, при нажатии клавиши [key S] (не важно, каков регистр и раскладка) её скан-код будет равен "S".charCodeAt(0)
.
С другими символами, например, знаками пунктуации, ситуация тоже понятна. Есть таблица кодов, которую можно взять, например, из статьи Джона Уолтера: JavaScript Madness: Keyboard Events, или же можно нажать на нужную клавишу на тестовом стенде и получить код.
Когда-то в этих кодах была масса кросс-браузерных несовместимостей. Сейчас всё проще -- таблицы кодов в различных браузерах почти полностью совпадают.
Но некоторые несовместимости, всё же, остались. Вы можете увидеть их в таблице ниже. Слева -- клавиша с символом, а справа -- скан-коды в различных браузерах.
Клавиша | 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
:
//+ 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
.
В действии:
<input onkeypress="this.nextSibling.innerHTML = getChar(event)+''"><b></b>
[warn header="Неправильный getChar
"]
В сети вы можете найти другую функцию того же назначения:
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
:
Попробуйте что-нибудь ввести в этих полях:
<input *!*onkeydown="return false"*/!* type="text" size="30">
<input *!*onkeypress="return false"*/!* type="text" size="30">
Попробуйте что-нибудь ввести в этих полях (не получится):
При тестировании на стенде вы можете заметить, что отмена действия браузера при keydown
также предотвращает само событие keypress
.
[warn header="При keydown/keypress
значение ещё старое"]
На момент срабатывания keydown/keypress
клавиша ещё не обработана браузером.
Поэтому в обработчике значение input.value
-- старое, т.е. до ввода. Это можно увидеть в примере ниже. Вводите символы abcd..
, а справа будет текущее input.value
: abc..
А что, если мы хотим обработать input.value
именно после ввода? Самое простое решение -- использовать событие keyup
, либо запланировать обрабочик через setTimeout(..,0)
.
[/warn]
[warn header="Не для работы с формами"] Распространённая ошибка -- использовать события клавиатуры для работы с полями ввода в формах.
Это неправильно. События клавиатуры предназначены именно для работы с клавиатурой. Да, их можно использовать и для <input>
, но будут побочные эффекты.
Например, текст может быть вставлен мышкой, при помощи правого клика и меню, без единого нажатия клавиши. И как нам помогут события клавиатуры?
Некоторые мобильные устройства не генерируют keypress/keydown
, а сразу вставляют текст в поле. Отменить ввод для них таким образом нельзя, как и вообще отловить его.
Далее мы разберём события для элементов форм, которые позволяют работать с вводом в формы правильно. [/warn]
Отмена любых действий
Отменять можно не только символ, а любое действие клавиш.
Например:
- При отмене [key Backspace] -- символ не удалится.
- При отмене [key PageDown] -- страница не прокрутится.
- При отмене [key Tab] -- курсор не перейдёт на следующее поле.
Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь -- те, которые происходят на уровне операционной системы. Комбинация Alt+F4 инициирует закрытие браузера в Windows, что бы мы ни делали в JavaScript.
Демо: перевод символа в верхний регистр
В примере ниже действие браузера отменяется с помощью return false
, а вместо него в input
добавляется значение в верхнем регистре:
<input id="my" type="text" size="2">
<script>
document.getElementById('my').onkeypress = function(e) {
// спец. сочетание - не обрабатываем
if (e.ctrlKey || e.altKey || e.metaKey) return;
var char = getChar(e);
if (!char) return; // спец. символ - не обрабатываем
this.value = char.toUpperCase();
return false;
};
</script>
В действии:
Особенности клавиш и ОС [#keyboard-events-order]
События keydown/keyup
возникают всегда. А keypress
-- по-разному.
Есть три основных категории клавиш, работа с которыми отличается.
Перечислим их в таблице, обращая основное внимание на особенности работы с ними.
Категория | Примеры | События | Описание |
---|---|---|---|
Печатные клавиши | [key S] [key 1] [key ,] | `keydown` `keypress` `keyup` | Нажатие вызывает `keydown` и `keypress`.
Когда клавишу отпускают, срабатывает `keyup`.
Исключение -- CapsLock под MacOS, он глючит неимоверно:
|
Специальные клавиши | [key Alt] [key Esc] [key ⇧] | `keydown` `keyup` | Нажатие вызывает `keydown`.
Когда клавишу отпускают, срабатывает `keyup`.
Некоторые браузеры могут дополнительно генерировать и На практике это не доставляет проблем, так как для специальных клавиш мы всегда используем |
Сочетания с печатной клавишей |
[key Alt+E] [key Ctrl+У] [key Cmd+1] |
`keydown` `keypress?` `keyup` |
Браузеры под Windows -- не генерируют `keypress`, браузеры под MacOS -- генерируют.
Кроме того, если сочетание вызвало браузерное действие или диалог ("Сохранить файл", "Открыть" и т.п., ряд диалогов можно отменить при |
Автоповтор
При долгом нажатии клавиши возникает автоповтор. По стандарту, должны генерироваться многократные события keydown (+keypress)
, и вдобавок стоять свойство repeat=true у объекта события.
То есть поток событий должен быть такой:
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`, а лучше вообще не трогать эту клавишу.
Для работы с вводом в формы, существуют события для форм, которые мы разберём позже. Их можно использовать как отдельно от событий клавиатуры, так и вместе с ними.
[head]
[/head]