如何使用JavaScript检测同时按下多个键?

224

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

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

问题是,当我按下右箭头键并按下空格键时,角色跳跃后停止移动。

我使用keydown函数来获取键盘按键事件。如何检查是否同时按下多个键?


3
这是一个自动打印所有按键的列表的网页演示:https://dev59.com/3mYr5IYBdhLWcg3wfKL_#13651016。 - Anderson Green
19个回答

406

注意: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 */
}

这段代码非常简单:由于计算机一次只能通过一个按键,因此创建一个数组来跟踪多个按键。然后可以使用该数组同时检查一个或多个按键。
为了解释清楚,假设您按下 AB,每个按键都会触发一个 keydown 事件,将 map[e.keyCode] 设置为 e.type == keydown 的值,该值评估为 truefalse。现在,map[65]map[66] 都设置为 true。当您松开 A 时,会触发 keyup 事件,导致相同的逻辑确定 map[65](A)的相反结果,即现在为 false,但由于 map[66](B)仍然处于“按下”状态(它没有触发 keyup 事件),因此它保持为 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元素,可以在JavaScript中轻松地通过element进行引用。
alert(element); // [Object HTMLDivElement]

您甚至不需要使用document.getElementById()$()来获取它。但为了兼容性,更广泛地推荐使用jQuery的$()

只需确保


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - user3015682
@user3015682,“list”只是一个你自己更新的数组。其他选项卡无法访问这些键,如果你担心键盘记录器,任何JS代码都无法防止外部程序读取你的键盘输入。即使你删除了数组,数据仍然存在于内存中,可以被读取。至于焦点,在C++中,你可以检测窗口失去焦点时并在发生这种情况时清除keysdown[]数组。如果JavaScript中有这样的事件(失去焦点),你可以使用它来在窗口失去焦点时每次清除数组。这将解决该错误。 - Braden Best
3
注意:keyCode已经过时 - 如果您切换到key,则可以获得按键的实际字符表示形式,这可能更好。 - Sean Vieira
1
@SeanVieira 另一方面,在 C 中也可以做一些奇怪的事情。例如,你知道 myString[5]5[myString] 是相同的吗?即使使用 -Wall -pedantic 编译选项,它也不会给你编译警告。这是因为 pointer[offset] 表示法获取指针,添加偏移量,然后解引用结果,使得 myString[5] 等同于 *(myString + 5) - Braden Best
1
@inorganik,您是指助手类吗?gist能像repos一样使用吗?为一个小代码片段创建整个repo会很繁琐。当然,我会制作一个gist。今晚我会尽量完成。大约是山区时间午夜左右。 - Braden Best
显示剩余37条评论

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

    } 

}

32

使用keydown事件来跟踪按下的键,并且使用keyup事件来跟踪键何时被释放。

可以参考这个例子: http://jsfiddle.net/vor0nwe/mkHsU/

(更新:为防止jsfiddle.net失效,我在此处重现代码:)

<ul id="log">
    <li>List of keys:</li>
</ul>

...以及使用jQuery的Javascript代码:

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

在这个例子中,我使用一个数组来跟踪哪些键被按下。在实际应用中,您可能希望在释放其关联键后删除每个元素。

请注意,虽然我在此示例中使用了jQuery使事情变得容易,但在使用“原始的”JavaScript时,该概念同样适用。


但是正如我所想,存在一个错误。如果你一直按住一个按钮,然后切换到另一个选项卡(或失去焦点),同时仍然按住该按钮,当你重新聚焦到脚本时,它会显示该按钮已被按下,即使它实际上没有被按下。 :D - XCS
4
你可以添加一个onblur事件处理程序,从数组中删除所有已按下的键。一旦失去焦点,需要再次按下所有键才能生效。不幸的是,JS没有相当于GetKeyboardState的功能。 - Martijn
谢谢,这种多键“模式”解决了比预期更多的错误:D - XCS
1
在Mac(Chrome)上使用Paste时遇到问题。它成功地获取了keydown 91(命令),keydown 86(v),但是然后只释放了91,使86保持按下状态。键列表:向上:91,向下:86。只有当第二次松开命令键时才会出现这种情况 - 如果我先松开它,它就会正确地注册两个键的keyup。 - James Alday
2
似乎当您同时按下三个或更多键时,它会停止检测到更多的按键,直到您松开其中一个键。(在Firefox 22中测试) - Qvcool
1
@JamesAlday 同样的问题。显然只影响 Mac 上的 Meta (OS) 键。请参见此处的问题#3:http://bitspushedaround.com/on-a-few-things-you-may-not-know-about-the-hellish-command-key-and-javascript-events/ - Don McCurdy

13

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

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

2
这对我来说是赢家。简洁,完全功能。如果您需要知道按下的键的名称,只需在keydown函数的末尾记录keyPressed对象即可。谢谢朋友! - lemonskunnk

9

这不是一种通用的方法,但在某些情况下它很有用。它适用于像 CTRL + something 或者 Shift + something 或者 CTRL + Shift + something 等组合键。

例如:当您想使用 CTRL + P 打印页面时,首先按下的键是 CTRL,然后是 P。同样的,对于 CTRL + SCTRL + U 和其他组合键也是如此。

document.addEventListener('keydown',function(e){
      
    //SHIFT + something
    if(e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('Shift + S');
                break;

        }
    }

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

            case 'KeyS':
                console.log('CTRL + Shift + S');
                break;

        }
    }

});


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

你会如何在 Angular 组件中实现这个功能? - ScipioAfricanus
我没有使用Angular的经验来回答你的问题@ScipioAfricanus。 - Oleg Novosad

4
这是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);


4

我喜欢使用这个代码片段,它非常有用于编写游戏输入脚本。

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

请注意,Array.includes 在旧版浏览器或现代 IE 中支持不佳:https://caniuse.com/#feat=array-includes - XCS
欢迎!您应该尝试直接回答所提出的问题,以便清晰明了。例如,您应该解释为什么将关键字符添加到keyMap中可以检测多个键。此外,在这种情况下,您应该解释何时需要调用checkGameKeys以及其目的是什么。 - NBTX
我不得不在事件处理程序的if语句后面添加checkGameKeys()才能使其正常工作。 - Simon O'Doherty

3
让 keydown 事件调用多个函数,每个函数检查特定的按键并做出相应的响应。
document.keydown = function (key) {

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

3
    $(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;
            }
        });

    });

1
非常感谢您的贡献,请尽量不要只发布代码,加上一些解释。 - Tim Rutter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接