Как определить, если несколько клавиш нажимаются одновременно с помощью JavaScript?
Я пытаюсь разработать игровой движок JavaScript, и я столкнулся с этой проблемой:
- когда я нажимаю пробел персонаж прыгает.
- когда я нажимаю & rightarrow; персонаж перемещается вправо.
проблема в том, что когда я нажимаю вправо, а затем нажимаю пробел, персонаж прыгает, а затем перестает двигаться.
Я использую keydown
функция для нажатия клавиши. Как я могу проверить, если есть сразу несколько клавиш нажаты?
9 ответов:
обнаружение нескольких нажатий клавиш легко, если вы понимаете концепцию
я делаю это так:
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, каждый стреляет a
keydown
событие, которое устанавливаетmap[e.keyCode]
стоимостьюe.type == keydown
, что означает либо правда или ложные. Теперь обаmap[65]
иmap[66]
используетсяtrue
. Когда вы отпуститеA
наkeyup
событие срабатывает, вызывая ту же логику, чтобы определить противоположный результат дляmap[65]
(A), который сейчас ложные, но посколькуmap[66]
(B) все еще "вниз" (он не вызвал событие keyup), он остается правда.The
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; } }
Примечание: Вы можете легко захватить элемент его .
<div id="element"></div>
этот создает элемент html, на который можно легко ссылаться в javascript с помощью
element
alert(element); // [Object HTMLDivElement]
вы даже не должны использовать
document.getElementById()
или$()
чтобы схватить его. Но ради совместимости, используйте jQuery$()
более широко рекомендуется.просто убедитесь, что script тег приходит после тела HTML. совет по оптимизации: большинство именитых веб-сайтов, поставить тег после тег для тела оптимизация. Это связано с тем, что тег скрипта блокирует дальнейшую загрузку элементов до завершения загрузки скрипта. Размещение его перед содержимым позволяет загружать содержимое заранее.
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'); }
Edit: это не самый читаемый фрагмент. Читаемость важна, так что вы могли бы попробуйте что-то вроде этого, чтобы сделать его легче на глазах:
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 цепи
если проверка на комбо различных количеств (например CtrlShift Altвведите и Ctrlвведите), положите меньшие комбо после большие комбо, или же меньшие комбо будут переопределять большие комбо, если они достаточно похожи. Пример:
// 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"
Gotcha: "эта комбинация клавиш продолжает активироваться, хотя я не нажимаю ключи"
при работе с предупреждениями или все, что принимает фокус из главного окна, вы можете включить
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 активирует облачный блокнот на расширение), вы могли бы также хотеть добавить
return false
послеmap = []
, так что пользователи вашего сайта не будут разочарованы, когда "дублировать файлы", на CtrlD, вместо закладки страницу.if(map[17] && map[68]){ // CTRL+D alert('The bookmark window didn\'t pop up!'); map = {}; return false; }
без
return false
в окне закладок б всплывающее окно, к ужасу пользователя.оператор return (new)
Итак, вы не всегда хотите, чтобы выйти из функции в этой точке. Вот почему для изучения указанных строк.
element.onevent
vselement.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 });
The
.onevent
свойство, кажется, переопределяет все и поведениеev.preventDefault()
иreturn false;
может быть довольно непредсказуемо.в любом случае, обработчики, зарегистрированные с помощью
addEventlistener
кажется, легче писать и рассуждать об этом.есть еще
attachEvent("onevent", callback)
из нестандартной реализации Internet Explorer, но это не устарело и даже не относится к JavaScript (это относится к эзотерическому языку под названием JScript). Это было бы в ваших интересах, чтобы избежать полиглот кода как можно больше.помощник класс
чтобы решить путаницу / жалобы, я написал "класс", который делает эту абстракцию ( pastebin link):
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
еслиkey
вниз, и 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, я создал gist.
обновление 2018-07-21 я уже некоторое время играю с декларативным стилем программирования, и этот способ теперь мой личный фаворит: скрипка,pastebin
как правило, он будет работать с делами, которые вы реально хотите (ctrl, alt, shift), но если вам нужно нажать, скажем,
a+w
в то же время, было бы не слишком сложно "объединить" подходы в многоключевой поиск.
я надеюсь, что это
подробно объяснил ответмини-блог был полезным :)
вы должны использовать keydown событие для отслеживания нажатых клавиш, и вы должны использовать keyup событие, чтобы отслеживать, когда ключи отпущены.
смотрите этот пример:http://jsfiddle.net/vor0nwe/mkHsU/
(обновление: я воспроизводю код здесь, в случае, если jsfiddle.net поручительство:) HTML:
<ul id="log"> <li>List of keys:</li> </ul>
...и Javascript (с помощью jQuery):
var log = $('#log')[0], pressedKeys = []; $(document.body).keydown(function (evt) { var li = pressedKeys[evt.keyCode]; if (!li) { li = log.appendChild(document.createElement('li')); pressedKeys[evt.keyCode] = li; } $(li).text('Down: ' + evt.keyCode); $(li).removeClass('key-up'); }); $(document.body).keyup(function (evt) { var li = pressedKeys[evt.keyCode]; if (!li) { li = log.appendChild(document.createElement('li')); } $(li).text('Up: ' + evt.keyCode); $(li).addClass('key-up'); });
В этом например, я использую массив, чтобы отслеживать, какие клавиши нажимаются. В реальном приложении, вы, возможно, захотите, чтобы
delete
каждый элемент, как только их связанный ключ был освобожден.обратите внимание, что хотя я использовал jQuery, чтобы облегчить себе работу в этом примере, концепция работает так же хорошо при работе в "сыром" Javascript.
я использовал этот способ (пришлось проверить, где находится Shift + Ctrl нажата):
// create some object to save all pressed keys var keys = { shift: false, ctrl: false }; $(document.body).keydown(function(event) { // save status of the button 'pressed' == 'true' if (event.keyCode == 16) { keys["shift"] = true; } else if (event.keyCode == 17) { keys["ctrl"] = true; } if (keys["shift"] && keys["ctrl"]) { $("#convert").trigger("click"); // or do anything else } }); $(document.body).keyup(function(event) { // reset status of the button 'released' == 'false' if (event.keyCode == 16) { keys["shift"] = false; } else if (event.keyCode == 17) { keys["ctrl"] = false; } });
document.onkeydown = keydown; function keydown (evt) { if (!evt) evt = event; if (evt.ctrlKey && evt.altKey && evt.keyCode === 115) { alert("CTRL+ALT+F4"); } else if (evt.shiftKey && evt.keyCode === 9) { alert("Shift+TAB"); } }
сделайте keydown даже вызов нескольких функций, причем каждая функция проверяет наличие определенного ключа и отвечает соответствующим образом.
document.keydown = function (key) { checkKey("x"); checkKey("y"); };
для тех кому нужен полный пример кода. Справа + слева добавлено
var keyPressed = {}; document.addEventListener('keydown', function(e) { keyPressed[e.key + e.location] = true; if(keyPressed.Shift1 == true && keyPressed.Control1 == true){ // Left shift+CONTROL pressed! keyPressed = {}; // reset key map } if(keyPressed.Shift2 == true && keyPressed.Control2 == true){ // Right shift+CONTROL pressed! keyPressed = {}; } }, false); document.addEventListener('keyup', function(e) { keyPressed[e.key + e.location] = false; keyPressed = {}; }, false);
если одна из клавиш нажата Alt / Crtl / Shift, вы можете использовать этот метод:
document.body.addEventListener('keydown', keysDown(actions) ); function actions() { // do stuff here } // simultaneous pressing Alt + R function keysDown (cb) { return function (zEvent) { if (zEvent.altKey && zEvent.code === "KeyR" ) { return cb() } } }
Я бы попробовал добавить
keypress
Event
обработчик наkeydown
. Например:window.onkeydown = function() { // evaluate key and call respective handler window.onkeypress = function() { // evaluate key and call respective handler } } window.onkeyup = function() { window.onkeypress = void(0) ; }
Это просто предназначено для иллюстрации шаблона; я не буду вдаваться в подробности здесь (особенно не в браузер конкретного level2+
Event
Регистрация).сообщение назад, пожалуйста, помогает ли это или нет.