Примечание: keyCode больше не поддерживается .
Обнаружение множественных нажатий клавиш легко, если вы понимаете концепцию
То, как я это делаю, выглядит так:
var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
e = e || event; // to deal with IE
map[e.keyCode] = e.type == 'keydown';
/* insert conditional here */
}
Этот код очень прост: поскольку компьютер одновременно выполняет только одно нажатие клавиши, создается массив для отслеживания нескольких клавиш. Затем этот массив можно использовать для проверки одного или нескольких ключей одновременно.
Просто чтобы объяснить, скажем, вы нажимаете Aи B, каждый запускает keydown
событие, которое устанавливает map[e.keyCode]
значение e.type == keydown
, которое оценивается как истинное или ложное . Теперь оба map[65]
и map[66]
настроены на true
. Когда вы отпускаете A
, keyup
событие запускается, в результате чего та же логика определяет противоположный результат для map[65]
(A), который теперь является ложным , но поскольку map[66]
(B) все еще находится в состоянии «вниз» (он не вызвал событие keyup), это остается правдой .
map
Массив, через оба события, выглядит следующим образом :
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
Есть две вещи, которые вы можете сделать сейчас:
A) Ключевой регистратор ( пример ) может быть создан как справочник для последующего использования, когда вы хотите быстро выяснить один или несколько кодов клавиш. Предполагая, что вы определили элемент html и указали на него с помощью переменной element
.
element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
if(map[i]){
element.innerHTML += '<hr>' + i;
}
}
Примечание. Вы можете легко получить элемент по его id
атрибуту.
<div id="element"></div>
Это создает элемент HTML, на который можно легко ссылаться в JavaScript с помощью element
alert(element); // [Object HTMLDivElement]
Вы даже не должны использовать document.getElementById()
или $()
захватить это. Но для совместимости $()
рекомендуется использовать jQuery .
Просто убедитесь, что тег script идет после тела HTML. Совет по оптимизации : большинство известных сайтов ставят тег script после тега body для оптимизации. Это связано с тем, что тег script блокирует дальнейшую загрузку элементов до завершения загрузки скрипта. Размещение его перед контентом позволяет загружать контент заранее.
B (в этом и заключается ваш интерес). Вы можете проверить один или несколько ключей за раз, где /*insert conditional here*/
был, возьмите этот пример:
if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
alert('Control Shift C');
}
Изменить : это не самый читаемый фрагмент. Читаемость важна, поэтому вы можете попробовать что-то вроде этого, чтобы облегчить глаза:
function test_key(selkey){
var alias = {
"ctrl": 17,
"shift": 16,
"A": 65,
/* ... */
};
return key[selkey] || key[alias[selkey]];
}
function test_keys(){
var keylist = arguments;
for(var i = 0; i < keylist.length; i++)
if(!test_key(keylist[i]))
return false;
return true;
}
Использование:
test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')
Это лучше?
if(test_keys('ctrl', 'shift')){
if(test_key('A')){
alert('Control Shift A');
} else if(test_key('B')){
alert('Control Shift B');
} else if(test_key('C')){
alert('Control Shift C');
}
}
(конец редактирования)
Этот пример проверяет наличие CtrlShiftA, CtrlShiftBиCtrlShiftC
Это так же просто, как это :)
Ноты
Отслеживание ключевых кодов
Как правило, рекомендуется документировать код, особенно такие, как коды клавиш (например // CTRL+ENTER
), чтобы вы могли помнить, что они были.
Вы также должны поместить коды клавиш в том же порядке, что и документация ( CTRL+ENTER => map[17] && map[13]
, НЕ map[13] && map[17]
). Таким образом, вы никогда не запутаетесь, когда вам нужно вернуться и отредактировать код.
Гоча с цепями if-else
Если вы проверяете комбинации разных сумм (например, CtrlShiftAltEnterи CtrlEnter), поместите меньшие комбинации после больших, иначе меньшие комбинации будут заменять большие комбинации, если они достаточно похожи. Пример:
// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
alert('You found me');
}else if(map[13]){ // ENTER
alert('You pressed Enter. You win the prize!')
}
// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"
Попался: "Эта комбинация клавиш продолжает активироваться, хотя я не нажимаю клавиши"
При работе с оповещениями или чем-либо, что фокусируется на главном окне, вы можете включить map = []
сброс массива после выполнения условия. Это потому, что некоторые вещи, например alert()
, убирают фокус с главного окна и приводят к тому, что событие 'keyup' не запускается. Например:
if(map[17] && map[13]){ // CTRL+ENTER
alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:
if(map[17] && map[13]){ // CTRL+ENTER
alert('Take that, bug!');
map = {};
}
// The bug no longer happens since the array is cleared
Получил: браузер по умолчанию
Вот неприятная вещь, которую я нашел, с включенным решением:
Проблема: поскольку браузер обычно выполняет действия по умолчанию для сочетаний клавиш (например, CtrlDактивирует окно закладок или CtrlShiftCактивирует skynote на maxthon), вы также можете добавить return false
после map = []
, чтобы пользователи вашего сайта не разочаровались, когда «Дублирующийся файл» функция, которая ставится CtrlD, вместо этого добавляет в закладки страницу.
if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}
Без return false
, окно закладок будет всплывало, к ужасу пользователя.
Заявление о возврате (новый)
Итак, вы не всегда хотите выйти из функции в этот момент. Вот почему event.preventDefault()
функция есть. Он устанавливает внутренний флаг, который говорит интерпретатору не разрешать браузеру выполнять действие по умолчанию. После этого выполнение функции продолжается (тогда какreturn
сразу же выйдет из функции).
Поймите это различие, прежде чем вы решите, использовать ли return false
илиe.preventDefault()
event.keyCode
устарела
Пользователь SeanVieira указал в комментариях, чтоevent.keyCode
устарел.
Там он дал отличную альтернативу:, event.key
которая возвращает строковое представление нажатой клавиши, например, "a"
для Aили "Shift"
дляShift .
Я пошел вперед и приготовил инструмент для изучения указанных струн.
element.onevent
против element.addEventListener
Обработчики, зарегистрированные в, addEventListener
могут быть сложены, и вызываются в порядке регистрации, в то время как установка .onevent
непосредственно довольно агрессивна и переопределяет все, что у вас было ранее.
document.body.onkeydown = function(ev){
// do some stuff
ev.preventDefault(); // cancels default actions
return false; // cancels this function as well as default actions
}
document.body.addEventListener("keydown", function(ev){
// do some stuff
ev.preventDefault() // cancels default actions
return false; // cancels this function only
});
.onevent
Свойство кажется переопределить все и поведение ev.preventDefault()
иreturn false;
может быть весьма непредсказуемым.
В любом случае, обработчики, зарегистрированные через addEventlistener
кажется, легче написать и рассуждать.
Это также attachEvent("onevent", callback)
из нестандартной реализации Internet Explorer, но это не рекомендуется, и даже не относится к JavaScript (это относится к эзотерическому языку, называемому JScript ). Было бы в ваших интересах избегать как можно большего количества полиглотов.
Вспомогательный класс
Чтобы устранить путаницу / жалобы, я написал «класс», который делает эту абстракцию ( ссылка для вставки ):
function Input(el){
var parent = el,
map = {},
intervals = {};
function ev_kdown(ev)
{
map[ev.key] = true;
ev.preventDefault();
return;
}
function ev_kup(ev)
{
map[ev.key] = false;
ev.preventDefault();
return;
}
function key_down(key)
{
return map[key];
}
function keys_down_array(array)
{
for(var i = 0; i < array.length; i++)
if(!key_down(array[i]))
return false;
return true;
}
function keys_down_arguments()
{
return keys_down_array(Array.from(arguments));
}
function clear()
{
map = {};
}
function watch_loop(keylist, callback)
{
return function(){
if(keys_down_array(keylist))
callback();
}
}
function watch(name, callback)
{
var keylist = Array.from(arguments).splice(2);
intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
}
function unwatch(name)
{
clearInterval(intervals[name]);
delete intervals[name];
}
function detach()
{
parent.removeEventListener("keydown", ev_kdown);
parent.removeEventListener("keyup", ev_kup);
}
function attach()
{
parent.addEventListener("keydown", ev_kdown);
parent.addEventListener("keyup", ev_kup);
}
function Input()
{
attach();
return {
key_down: key_down,
keys_down: keys_down_arguments,
watch: watch,
unwatch: unwatch,
clear: clear,
detach: detach
};
}
return Input();
}
Этот класс не делает все и не будет обрабатывать все возможные варианты использования. Я не библиотекарь. Но для общего интерактивного использования это должно быть хорошо.
Чтобы использовать этот класс, создайте экземпляр и укажите его на элемент, с которым вы хотите связать ввод с клавиатуры:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
То, что это будет делать, это присоединить новый слушатель ввода к элементу с помощью #txt
(давайте предположим, что это текстовое поле) и установить точку наблюдения для комбинации клавиш Ctrl+5
. Когда оба Ctrl
и 5
выключены, "FIVE "
будет вызвана функция обратного вызова (в данном случае это функция, которая добавляет текстовую область). Обратный вызов связан с именем print_5
, поэтому, чтобы удалить его, вы просто используете:
input_txt.unwatch("print_5");
Чтобы отсоединить input_txt
от txt
элемента:
input_txt.detach();
Таким образом, сборщик мусора может забрать объект ( input_txt
), если он будет выброшен, и у вас не останется старый прослушиватель событий зомби.
Для краткости приведу краткую ссылку на API класса, представленный в стиле C / Java, чтобы вы знали, что они возвращают и какие аргументы они ожидают.
Boolean key_down (String key);
Возвращает true
if key
down, false в противном случае.
Boolean keys_down (String key1, String key2, ...);
Возвращает, true
если все ключи не key1 .. keyN
работают, в противном случае - false.
void watch (String name, Function callback, String key1, String key2, ...);
Создает «точку наблюдения», так что нажатие всех keyN
вызовет обратный вызов
void unwatch (String name);
Удаляет указанную точку наблюдения через ее имя
void clear (void);
Стирает кеш "ключами вниз". Эквивалент map = {}
выше
void detach (void);
Отсоединение ev_kdown
и ev_kup
слушатели от родительского элемента, что позволяет безопасно избавиться от экземпляра
Обновление 2017-12-02 В ответ на запрос опубликовать это на github я создал суть .
Обновление 2018-07-21 Некоторое время я играл с декларативным стилевым программированием, и теперь этот путь мой любимый: fiddle , pastebin.
Как правило, он будет работать с теми случаями, которые вы реально хотите (ctrl, alt, shift), но если вам нужно нажать, скажем, a+w
в то же время, не составит труда объединить подходы в мульти-ключ-поиска.
Надеюсь, этот подробный мини-блог с ответом был полезен :)