Как определить, если несколько клавиш нажимаются одновременно с помощью JavaScript?


Я пытаюсь разработать игровой движок JavaScript, и я столкнулся с этой проблемой:

  • когда я нажимаю пробел персонаж прыгает.
  • когда я нажимаю & rightarrow; персонаж перемещается вправо.

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

Я использую keydown функция для нажатия клавиши. Как я могу проверить, если есть сразу несколько клавиш нажаты?

9 130

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 vs 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
});

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()
    }
  }
}

Я бы попробовал добавить keypressEvent обработчик на 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 Регистрация).

сообщение назад, пожалуйста, помогает ли это или нет.

case 65: //A
jp = 1;
setTimeout("jp = 0;", 100);

if(pj > 0) {
ABFunction();
pj = 0;
}
break;

case 66: //B
pj = 1;
setTimeout("pj = 0;", 100);

if(jp > 0) {
ABFunction();
jp = 0;
}
break;

Не самый лучший способ, я знаю.