如何检测是否使用JavaScript一次按下多个键?

时间:2011-03-05 11:07:36

标签: javascript keydown

我正在尝试开发JavaScript游戏引擎,我遇到了这个问题:

  • 当我按 SPACE 时,角色会跳跃。
  • 当我按时,角色向右移动。

问题在于,当我向右按,然后按空格键时,角色跳跃然后停止移动。

我使用keydown函数来按下键。如何检查是否一次按下多个键?

16 个答案:

答案 0 :(得分:276)

如果您了解概念

,则可以轻松进行多次击键检测

我这样做是这样的:

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的值,其值为 true false 。现在,map[65]map[66]都设置为true。当您放开A时,keyup事件将触发,导致相同的逻辑确定map[65](A)的相反结果,现在 false ,但由于map[66](B)仍在" down" (它没有触发键盘事件),它仍然是 true

map数组,通过这两个事件,如下所示:

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]

你现在可以做两件事:

A)当您想要快速找出一个或多个密钥代码时,可以创建密钥记录器(example)作为参考。假设您已定义了一个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元素,可以使用element

在javascript中轻松引用
alert(element); // [Object HTMLDivElement]

您甚至不必使用document.getElementById()$()来抓住它。但是为了兼容性,更广泛地推荐使用jQuery&#39; {

只需确保脚本标记位于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');
}

用法:

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

(编辑结束)


此示例检查 Ctrl Shift A Ctrl Shift B Ctrl Shift C

它就像那样简单:)

备注

跟踪KeyCodes

作为一般规则,最好记录代码,尤其是密钥代码(如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'); } } ),以便您记住它们是什么。

您还应该将密钥代码与文档(// CTRL+ENTER,而不是CTRL+ENTER => map[17] && map[13])放在一起。这样,当您需要返回并编辑代码时,您将不会感到困惑。

if-else链的问题

如果检查不同数量的组合(例如 Ctrl Shift Alt Enter Ctrl < / kbd> 输入),在较大的组合后放置较小的组合,否则较小的组合将覆盖较大的组合(如果它们足够相似)。例如:

map[13] && map[17]

Gotcha:&#34;即使我没有按下键,这个键组合仍会保持激活状态&#34;

当处理警报或从主窗口获得焦点的任何事情时,您可能希望在条件完成后包括// 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()

Gotcha:浏览器默认值

这是我发现的令人讨厌的事情,其中​​包括以下解决方案:

问题:由于浏览器通常对键组合有默认操作(例如 Ctrl D 激活书签窗口,或 Ctrl Shift C 在maxthon上激活skynote),您可能还想在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 之后添加return false,这样您网站的用户就不会感到沮丧&#34;重复文件&#34;功能,放在 Ctrl D 上,改为为该页面添加书签。

map = []

如果没有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()

用户SeanVieira在评论中指出event.keyCode已被弃用。

在那里,他提供了一个很好的选择:event.keyCode,它返回被按下的键的字符串表示,如{kbd> A 的event.key"a"对于 Shift

我继续做了tool来检查所说的琴弦。

"Shift" vs 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 }); 属性似乎覆盖了所有内容,.oneventev.preventDefault()的行为可能相当难以预测。

在任何一种情况下,通过return false;注册的处理程序似乎更容易编写和推理。

Internet Explorer的非标准实现也有addEventlistener,但这已经超出了弃用范围,甚至与JavaScript无关(它与一种叫做 JScript的深奥语言有关) )。尽可能避免使用多语言代码符合您的最佳利益。

辅助类

为了解决混淆/投诉,我写了一个&#34;类&#34;做这个抽象(pastebin link):

attachEvent("onevent", callback)

这门课不做任何事情,它不会处理每一个可以想象的用例。我不是图书馆的人。但对于一般的交互式使用它应该没问题。

要使用此类,请创建一个实例并将其指向要与键盘输入关联的元素:

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"); 元素(让我们假设它是一个textarea),并为关键组合#txt设置一个观察点。 。如果Ctrl+5Ctrl都关闭,则会调用您传入的回调函数(在本例中为将5添加到textarea的函数)。回调与名称"FIVE "相关联,因此要删除它,只需使用:

print_5

input_txt.unwatch("print_5"); 元素中分离input_txt

txt

这样,垃圾收集可以拾取对象(input_txt.detach(); ),如果它被丢弃,你就不会遗留旧的僵尸事件监听器。

为了彻底,这里是对C / Java风格的API类的快速参考,以便您知道它们返回的内容以及它们所期望的参数。

input_txt
     

如果Boolean key_down (String key); 关闭,则返回true,否则返回false。

key
     

如果所有键Boolean keys_down (String key1, String key2, ...); 都关闭,则返回true,否则返回false。

key1 .. keyN
     

创建一个&#34;观察点&#34;按下所有void watch (String name, Function callback, String key1, String key2, ...); 将触发回调

keyN
     

通过名称

删除所述观察点
void     unwatch (String name);
     

按下&#34;键&#34;缓存。相当于void clear (void); 以上

map = {}
     

从父元素中分离void detach (void); ev_kdown侦听器,从而可以安全地删除实例

更新2017-12-02 为了回应向github发布此请求的请求,我创建了gist

更新2018-07-21 我一直在使用声明式样式编程,这种方式现在是我个人的最爱:fiddle,{{3} }

通常情况下,它会处理你想要的情况(ctrl,alt,shift),但是如果你需要同时点击ev_kup,它就不会“#{1}}。太难了'#34;结合&#34;进入多键查找的方法。


我希望这个彻底解释回答迷你博客很有用:)

答案 1 :(得分:28)

您应该使用 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时,这个概念也同样适用。

答案 2 :(得分:11)

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");

    } 

}

答案 3 :(得分:6)

我用这种方式(必须检查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;
    }
});

答案 4 :(得分:2)

让keydown甚至调用多个函数,每个函数检查一个特定的键并做出适当的响应。

document.keydown = function (key) {

    checkKey("x");
    checkKey("y");
};

答案 5 :(得分:2)

    $(document).ready(function () {
        // using ascii 17 for ctrl, 18 for alt and 83 for "S"
        // ctr+alt+S
        var map = { 17: false, 18: false, 83: false };
        $(document).keyup(function (e) {
            if (e.keyCode in map) {
                map[e.keyCode] = true;
                if (map[17] && map[18] && map[83]) {
                    // Write your own code here, what  you want to do
                    map[17] = false;
                    map[18] = false;
                    map[83] = false;
                }
            }
            else {
                // if u press any other key apart from that "map" will reset.
                map[17] = false;
                map[18] = false;
                map[83] = false;
            }
        });

    });

答案 6 :(得分:1)

谁需要完整的示例代码。右+左添加

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

答案 7 :(得分:1)

如果按下其中一个键是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()
    }
  }
}

答案 8 :(得分:1)

我喜欢使用此代码段,它对于编写游戏输入脚本非常有用

var keyMap = [];

window.addEventListener('keydown', (e)=>{
    if(!keyMap.includes(e.keyCode)){
        keyMap.push(e.keyCode);
    }
})

window.addEventListener('keyup', (e)=>{
    if(keyMap.includes(e.keyCode)){
        keyMap.splice(keyMap.indexOf(e.keyCode), 1);
    }
})

function key(x){
    return (keyMap.includes(x));
}

function checkGameKeys(){
    if(key(32)){
        // Space Key
    }
    if(key(37)){
        // Left Arrow Key
    }
    if(key(39)){
        // Right Arrow Key
    }
    if(key(38)){
        // Up Arrow Key
    }
    if(key(40)){
        // Down Arrow Key
    }
    if(key(65)){
        // A Key
    }
    if(key(68)){
        // D Key
    }
    if(key(87)){
        // W Key
    }
    if(key(83)){
        // S Key
    }
}

答案 9 :(得分:1)

这是 Bradens 答案的实现。

var keys = {}
function handleKeyPress(evt) {
  let { keyCode, type } = evt || Event; // to deal with IE
  let isKeyDown = (type == 'keydown');
  keys[keyCode] = isKeyDown;

  // test: enter key is pressed down & shift isn't currently being pressed down 
  if(isKeyDown && keys[13] && !keys[16]){
    console.log('user pressed enter without shift')
  }
};

window.addEventListener("keyup", handleKeyPress);
window.addEventListener("keydown", handleKeyPress);

答案 10 :(得分:0)

我尝试在keypress上添加Event keydown处理程序。 E.g:

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注册)。

请回复一下这是否有帮助。

答案 11 :(得分:0)

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;

不是最好的方式,我知道。

答案 12 :(得分:0)

这不是通用方法,但在某些情况下很有用。对于 CTRL + something Shift + something CTRL +的组合很有用 Shift + 某物,等等。

示例:当您要使用 CTRL + P 打印页面时,始终按下的第一个键始终是 CTRL ,然后是 P 。与 CTRL + S CTRL + U 和其他组合相同。

document.addEventListener('keydown',function(e){

  //SHIFT + something
  if(e.shiftKey){
    switch(e.code){

      case 'KeyS':
        alert('Shift + S');
        break;

    }
  }

  //CTRL + SHIFT + something
  if(e.ctrlKey && e.shiftKey){
    switch(e.code){

      case 'KeyS':
        alert('CTRL + Shift + S');
        break;

    }
  }

});

答案 13 :(得分:0)

只需使事情更稳定:

var keys = [];
$(document).keydown(function (e) { 
    if(e.which == 32 || e.which == 70){
    keys.push(e.which);
    if(keys.length == 2 && keys.indexOf(32) != -1 && keys.indexOf(70) != -1){
        
        
        
        alert("it WORKS !!"); //MAKE SOMETHING HERE---------------->
        
        
        
        keys.length = 0;
    }else if((keys.indexOf(32) == -1 && keys.indexOf(70) != -1) || (keys.indexOf(32) != -1 && keys.indexOf(70) == -1) && (keys.indexOf(32) > 1 || keys.indexOf(70) > 1)){
    }else{
        keys.length = 0;
    }
}else{
    keys.length = 0;
}
});

答案 14 :(得分:0)

如果你想用控制键找到任何按键事件,你可以这样做

onkeypress = (e) =>{
console.log(e);
if(e.ctrlKey && e.code == "KeyZ"){
    document.write("do somthing")
} }

答案 15 :(得分:-1)

Easiest, and most Effective Method

//check key press
    function loop(){
        //>>key<< can be any string representing a letter eg: "a", "b", "ctrl",
        if(map[*key*]==true){
         //do something
        }
        //multiple keys
        if(map["x"]==true&&map["ctrl"]==true){
         console.log("x, and ctrl are being held down together")
        }
    }

//>>>variable which will hold all key information<<
    var map={}

//Key Event Listeners
    window.addEventListener("keydown", btnd, true);
    window.addEventListener("keyup", btnu, true);

    //Handle button down
      function btnd(e) {
      map[e.key] = true;
      }

    //Handle Button up
      function btnu(e) {
      map[e.key] = false;
      }

//>>>If you want to see the state of every Key on the Keybaord<<<
    setInterval(() => {
                for (var x in map) {
                    log += "|" + x + "=" + map[x];
                }
                console.log(log);
                log = "";
            }, 300);