Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

События клавиатуры

  1. Тестовый стенд
  2. Виды и свойства событий
    1. keydown и keyup
    2. Какими бывают скан-коды?
    3. keypress
    4. Получение символа в keypress
  3. Отмена пользовательского ввода
    1. Отмена спец. действий
    2. Демо: перевод символа в верхний регистр
  4. Порядок наступления событий
  5. Автоповтор
  6. Итого, рецепты

Здесь мы рассмотрим основные «клавиатурные» события и работу с ними.

Тестовый стенд

Для того, чтобы лучше понять, как работают события клавиатуры, можно использовать тестовый стенд.

Попробуйте различные варианты нажатия клавиш в текстовом поле.

Предотвратить действие по умолчанию для:      
Игнорировать:      
Сфокусируйтесь на поле и нажмите какую-нибудь клавишу.

Журнал:

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):

  1. Во всех браузерах, кроме IE, у события keypress есть свойство charCode, которое содержит код символа.
  2. При этом у Opera есть некоторые баги со специальными клавишами. Для некоторых из них, она «забывает» указать charCode, например, для «Backspace». А другие браузеры в этом случае код указывают.
  3. Браузер IE для keypress не устанавливает charCode. Вместо этого он записывает код символа в keyCodekeydown/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 , keydown
keypress

keyup
Нажатие вызывает keydown и keypress.
Когда клавишу отпускают, срабатывает keyup.

Исключение — CapsLock под MacOS.

Раскрыть детали CapsLock под MacOS

Обработка CapsLock под MacOS глючит неимоверно:

  • В Safari/Chrome: при включении только keydown, при отключении только keyup.
  • В Firefox: при включении и отключении только keydown.
  • В Opera: вообще нет события.

Попробуйте на тестовом стенде.

Специальные клавиши Alt Esc keydown

keyup
Нажатие вызывает keydown.
Когда клавишу отпускают, срабатывает keyup.

Некоторые браузеры могут генерировать и keypress, например IE для Esc. Попробуйте нажать Esc на тестовом стенде в разных браузерах — и увидите: в IE keypress есть, а в остальных — нет.

Сочетания с печатной клавишей

Alt+E
Ctrl+У
Cmd+1

keydown
keypress?

keyup
Браузеры под Windows — не генерируют keypress, браузеры под MacOS — генерируют.

Если сочетание вызвало браузерный диалог («Сохранить файл», «Открыть» и т.п.), то keypress/keyup не будет.

В русской раскладке под MacOS в браузерах Safari/Firefox сочетания с Ctrl/Cmd не дают keyCode (это ошибка в браузерах). При этом для Cmd происходит keypress с английским кодом символа. Для проверки нажмите на тестовом стенде выше Ctrl + ф или Cmd + ф.

Таким образом, если использовать для обработки сочетаний лишь keydown, то в русской раскладке под MacOS в Safari/Firefox будут проблемы. Для сочетаний с Cmd их можно обойти, добавив обработку keypress.

Автоповтор

При долгом нажатии клавиши возникает автоповтор. Генерируются многократные события keydown (+keypress):

keydown
keypress
keydown
keypress
..повторяется, пока клавиша не отжата...
keyup

Под Linux(GTK) генерируется и keyup:

keydown
keypress
keyup
keydown
keypress
keyup
..повторяется, пока клавиша не отжата...
keyup

Итого, рецепты

Если обобщить данные из этой таблицы и статьи, то можно сделать ряд выводов:

  1. Для проверки клавиши или сочетания клавиш — используем keydown. Коды символов в разных браузерах одинаковы, за исключением нескольких.
  2. Ловля CapsLock глючит под MacOS. Её можно организовать сделать при помощи проверки navigator.userAgent и navigator.platform (кроме Opera).
  3. Для реализации горячих клавиш, включая сочетания — используем 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)
);

Демо: tutorial/browser/events/multikeys.html

Ход решения
Решение
Ход решения
  • Функция runOnKeys — с переменным числом аргументов. Для их получения используйте arguments.
  • Используйте два обработчика: document.onkeydown и document.onkeyup. Первый отмечает нажатие клавиши в объекте pressed = {}, устанавливая pressed[keyCode] = true а второй — удаляет это свойство. Если все клавиши с кодами из arguments нажаты — запускайте func.
  • Возникнет проблема с повторным нажатием сочетания клавиш после alert, решите её.
Решение

См. также:

Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!