- Тестовый стенд
- Виды и свойства событий
- Отмена пользовательского ввода
- Порядок наступления событий
- Автоповтор
- Итого, рецепты
Здесь мы рассмотрим основные «клавиатурные» события и работу с ними.
Тестовый стенд
Для того, чтобы лучше понять, как работают события клавиатуры, можно использовать тестовый стенд.
Попробуйте различные варианты нажатия клавиш в текстовом поле.
document.getElementById('kinput').onkeydown = khandle;
document.getElementById('kinput').onkeyup = khandle;
document.getElementById('kinput').onkeypress = khandle;
function khandle(e) {
e = e || event;
if (document.forms.keyform[e.type + 'Ignore'].checked) return;
var evt = e.type;
while (evt.length < 10) evt += ' '
showmesg(evt +
' keyCode=' + e.keyCode +
' which=' + e.which +
' charCode=' + e.charCode +
' char=' + String.fromCharCode(e.keyCode || e.charCode) +
(e.shiftKey ? ' +shift' : '') +
(e.ctrlKey ? ' +ctrl' : '') +
(e.altKey ? ' +alt' : '') +
(e.metaKey ? ' +meta' : ''), 'key'
)
if (document.forms.keyform[e.type + 'Stop'].checked) {
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
}
}
По мере чтения статьи, если возникнут вопросы — возвращайтесь к этому стенду.
Виды и свойства событий
keydown и keyup
События keydown/keyup происходят при нажатии/отпускании клавиши и позволяют получить её скан-код в свойстве keyCode.
Скан-код клавиши одинаков в любой раскладке и в любом регистре. Например, клавиша z может означать символ "z", "Z" или "я", "Я" в русской раскладке, но её скан-код будет всегда одинаков: 90.
В действии:
<input onkeydown="this.nextSibling.innerHTML = event.keyCode"><b></b>
Какими бывают скан-коды?
Для буквенно-цифровых клавиш, скан-код будет равен коду соответствующей заглавной английской буквы/цифры.
Например, при нажатии клавиши S (не важно, каков регистр и раскладка) её скан-код будет равен "S".charCodeAt(0).
С другими символами, например, знаками пунктуации, ситуация тоже понятна. Есть таблица кодов, которую в можно взять, например, из статьи Джона Уолтера: JavaScript Madness: Keyboard Events, или же можно нажать на нужную клавишу на тестовом стенде и получить код.
Когда-то в этих кодах была масса кросс-браузерных несовместимостей. Сейчас всё проще — таблицы кодов в различных браузерах почти полностью совпадают.
Но некоторые несовместимости, всё же, остались. Вы можете увидеть их в таблице ниже. Слева — клавиша с символом, а справа — скан-коды в различных браузерах.
| Клавиша | Firefox | Safari/Chrome/IE | Opera |
|---|---|---|---|
; |
59 | 186 | 59 |
= |
107 | 187 | 61 |
- |
109 | 189 | 45 |
, |
188 | 188 | 44 |
. |
190 | 190 | 46 |
/ |
191 | 191 | 47 |
\ |
220 | 220 | 92 |
keypress
Событие keypress возникает сразу после keydown, если нажата символьная клавиша, т.е. нажатие приводит к появлению символа.
Любые буквы, цифры генерируют keypress. Управляющие клавиши, такие как Ctrl, Shift, F1, F2.. — keypress не генерируют.
Событие keypress позволяет получить код символа. В отличие от скан-кода, он специфичен именно для символа и различен для "z" и "я".
Код символа хранится в свойствах: charCode и which. Здесь скрывается целое «гнездо» кросс-браузерных несовместимостей, разбираться с которыми нет никакого смысла — запомнить сложно, а на практике нужна лишь одна «правильная» функция, позволяющая получить код везде.
Получение символа в keypress
Кросс-браузерная функция для получения символа из события keypress:
// 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, которое содержит код символа. - При этом у Opera есть некоторые баги со специальными клавишами. Для некоторых из них, она «забывает» указать
charCode, например, для «Backspace». А другие браузеры в этом случае код указывают. - Браузер 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>
getCharВ сети вы можете найти другую функцию того же назначения:
function getChar(event) {
return String.fromCharCode(event.keyCode || event.charCode);
}
Она работает неверно для многих специальных клавиш, потому что не фильтрует их. Например, она возвращает символ амперсанда "&", когда нажата клавиша ‘Стрелка Вверх’. Кроме того, она глючит на Backspace в Opera.
Как и у других событий, связанных с пользовательским вводом, поддерживаются свойства shiftKey, ctrlKey, altKey и metaKey.
Они установлены в true, если нажаты клавиши-модификаторы — соответственно, Shift, Ctrl, Alt и Cmd для Mac.
Отмена пользовательского ввода
Появление символа можно предотвратить, если отменить действие браузера на keydown/keypress:
Попробуйте что-нибудь ввести в этих полях: <input *!*onkeydown="return false"*/!* type="text" size="30"> <input *!*onkeypress="return false"*/!* type="text" size="30">Попробуйте что-нибудь ввести в этих полях (не получится):
В IE и Safari/Chrome отмена действия браузера при keydown также предотвращает само событие keypress.
Некоторые мобильные устройства не генерируют keypress/keydown, а сразу вставляют текст в поле. Отменить ввод для них таким образом нельзя. В следующих главах мы разберём события для элементов форм, которые позволяют реагировать на такой ввод.
keydown/keypress значение ещё староеНа момент срабатывания keydown/keypress клавиша ещё не обработана браузером.
Поэтому, в частности, значение input.value — старое, т.е. до ввода. Это можно увидеть в в примере ниже. Вводите символы abcd.., а справа будет текущее input.value: abc..
А что, если мы хотим обработать input.value именно после ввода? В следующих главах мы рассмотрим, как это сделать, используя трюк с setTimeout(..,0).
Отмена спец. действий
Отменять можно действие любых клавиш. Например, при отмене Backspace — символ не удалится.
Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь — те, которые происходят на уровне операционной системы. Комбинация Alt+F4 инициирует закрытие браузера в большинстве операционных систем, что бы вы ни делали в JavaScript.
Демо: перевод символа в верхний регистр
В примере ниже, действие браузера отменяется с помощью return false, а вместо него в input добавляется значение в верхнем регистре:
<input id="my" type="text" size="2">
<script>
document.getElementById('my').onkeypress = function(e) {
e = e || window.event;
var char = getChar(event || window.event);
// спец. сочетание - не обрабатываем
if (e.ctrlKey || e.altKey || e.metaKey) return;
if (!char) return; // спец. символ - не обрабатываем
this.value = char.toUpperCase();
return false;
}
</script>
В действии:
Создайте поле, которое позволяет вводить с клавиатуры только цифры. Пример ниже.
В поле должны нормально работать специальные клавиши Delete/Backspace и сочетания c Ctrl/Alt.
Исходный документ: tutorial/browser/events/numeric-input-src/index.html.
Нам нужно событие keypress, так как по скан-коду мы не отличим, например, клавишу '2' обычную и в верхнем регистре (символ '@').
Нужно отменять действие по умолчанию (т.е. ввод), если введена не цифра.
Нам нужно проверять символы при вводе, поэтому, будем использовать событие keypress.
Алгоритм такой: получаем символ и проверяем, является ли он цифрой. Если не является, то отменяем действие по умолчанию.
Кроме того, игнорируем специальные символы и нажатия со включенным Ctrl/Alt.
Итак, вот решение:
input.onkeypress = function(e) {
e = e || event;
if (e.ctrlKey || e.altKey || e.metaKey) return;
var chr = getChar(e);
// с null надо осторожно в неравенствах,
// т.к. например null >= '0' => true
// на всякий случай лучше вынести проверку chr == null отдельно
if (chr == null) return;
if (chr < '0' || chr > '9') {
return false;
}
}
Полное решение тут: tutorial/browser/events/numeric-input/index.html.
Порядок наступления событий
События keydown/keyup возникают всегда. А keypress — по-разному.
Есть три основных варианта нажатия клавиш. Они перечислены в следующей таблице.
| Тип | Нажать на стенде | События | Описание |
|---|---|---|---|
| Печатные клавиши | S 1 , |
keydownkeypress– keyup |
Нажатие вызывает keydown и keypress.Когда клавишу отпускают, срабатывает keyup.
Исключение — CapsLock под MacOS. |
| Специальные клавиши | Alt Esc ⇧ |
keydown– keyup |
Нажатие вызывает keydown.Когда клавишу отпускают, срабатывает keyup.
Некоторые браузеры могут генерировать и |
| Сочетания с печатной клавишей |
|
keydownkeypress?– keyup |
Браузеры под Windows — не генерируют keypress, браузеры под MacOS — генерируют.
Если сочетание вызвало браузерный диалог («Сохранить файл», «Открыть» и т.п.), то В русской раскладке под MacOS в браузерах Safari/Firefox сочетания с Таким образом, если использовать для обработки сочетаний лишь |
Автоповтор
При долгом нажатии клавиши возникает автоповтор. Генерируются многократные события keydown (+keypress):
keydown keypress keydown keypress ..повторяется, пока клавиша не отжата... keyup
Под Linux(GTK) генерируется и keyup:
keydown keypress keyup keydown keypress keyup ..повторяется, пока клавиша не отжата... keyup
Итого, рецепты
Если обобщить данные из этой таблицы и статьи, то можно сделать ряд выводов:
- Для проверки клавиши или сочетания клавиш — используем
keydown. Коды символов в разных браузерах одинаковы, за исключением нескольких. - Ловля CapsLock глючит под MacOS. Её можно организовать сделать при помощи проверки
navigator.userAgentиnavigator.platform(кроме Opera). - Для реализации горячих клавиш, включая сочетания — используем
keydown.
Проблемы здесь также возникают в MacOS. Для обработки сочетаний сCmdв русской раскладке требуется еще иkeypress. Попробуйте сами на стенде и увидите.
Для обработки именно символа можно использовать keypress, но на момент этого события символ еще не введен в поле. Плюс — в том, что его ввод можно отменить. Минус — в том, что непонятно, каким будет итоговое значение поля, ведь символ мог быть введён в любом месте (на текущей позиции курсора).
Конечно, есть способы определить текущую позицию курсора. Ну, или можно воспользоваться событиями формы и трюком с setTimeout(..,0).
Создайте функцию runOnKeys(func, code1, code2, ... code_n), которая запускает func при одновременном нажатии клавиш со скан-кодами code1, code2, …, code_n.
Например, код ниже выведет alert при одновременном нажатии клавиш "Q" и "W" (в любом регистре, в любой раскладке)
runOnKeys(
function() { alert("Привет!") },
"Q".charCodeAt(0),
"W".charCodeAt(0)
);
- Функция
runOnKeys— с переменным числом аргументов. Для их получения используйтеarguments. - Используйте два обработчика:
document.onkeydownиdocument.onkeyup. Первый отмечает нажатие клавиши в объектеpressed = {}, устанавливаяpressed[keyCode] = trueа второй — удаляет это свойство. Если все клавиши с кодами изargumentsнажаты — запускайтеfunc. - Возникнет проблема с повторным нажатием сочетания клавиш после
alert, решите её.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.