en.javascript.info/2-ui/3-event-details/8-keyboard-events/article.md
2015-01-06 10:54:30 +03:00

17 KiB
Raw Blame History

Клавиатура: 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):

  1. Во всех браузерах, кроме IE, у события `keypress` есть свойство `charCode`, которое содержит код символа.
  2. Браузер 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, он глючит неимоверно:

  • В 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 у объекта события.

То есть поток событий должен быть такой:

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`, а лучше вообще не трогать эту клавишу.

Для работы с вводом в формы, существуют события для форм, которые мы разберём позже. Их можно использовать как отдельно от событий клавиатуры, так и вместе с ними.

[head]

[/head]